Страницы

Поиск по вопросам

четверг, 12 декабря 2019 г.

Const модификатор в сигнатуре функции

#cpp #language_lawyer


Добрый день, 

#include 
using namespace std;


void f_num(int x)
{
    cout << "int x" << endl;
}
void f_num(const int x)
{
    cout << "const int x" << endl;
}

void f_ptr(int* x)
{
    cout << "int* x" << endl;
}
void f_ptr(const int* x)
{
    cout << "const int* x" << endl;
}


int main()
{
    int* pointer = (int*)15;
    const int* pointer2 = (int*)15;

    int number = 0;
    const int number2 = 15;

    f_num(number);
    f_num(number2);

    f_ptr(pointer);
    f_ptr(pointer2);
}


Почему для указателей допустима перегрузка через модификатор параметра const, а для
объекта по значению нет.
    


Ответы

Ответ 1



В данном случае для компилятор просто воспринимает идентичными void f_num(int x); void f_num(const int x); потому что на самом деле при таком вызове создается копия передаваемой переменной, и компилятору без разницы создавать её из const или не const переменной, он просто не сможет определить какую конкретно функцию вызывать. Вы можете передавать переменную по ссылке (не создавая копию), тогда сработает: void f_num(int &x); void f_num(const int &x);

Ответ 2



Потому, что в С++/С аргументы передаются в функцию по значению. Поэтому с точки зрения вызывающего кода объявления void f(const int); и void f(int); совершенно идентичны. Это не так в случае указателя: void f(int *ptr); имеет право изменять данные, на которые указывает ptr, а void f(const int *ptr); // аргумент - указатель на константу int, то же, что и f(int const *ptr); Не имеет такого права (объект, на который указывает ptr - константный). Если говорить о константном указателе: void f(int * const ptr); В этом случае функция не имеет права изменять сам указатель ptr, при этом вполне может изменить данные, на которые он указывает, но поскольку при вызове все равно создается его копия - то компилятор не отличит его от сигнатуры void f(int * ptr); Вообще, модификатор const действует на "слово" непосредственно слева от него (кроме случая, когда он идет первым - в этом случае - на "слово" справа). https://stackoverflow.com/questions/9779540/c-const-pointer-declaration Кстати, для внутренней реализации объявления f(int i) и f(const i) различны: void f1(const int i) { i++; } Не скомпилируется, а void f2(int i) { i++; } скомпилируется прекрасно.

Ответ 3



Стандарт С++ 13.1/(3.4) Parameter declarations that differ only in the presence or absence of const and/or volatile are equivalent. That is, the const and volatile type-specifiers for each parameter type are ignored when determining which function is being declared, defined, or called. ... Only the const and volatile type-specifiers at the outermost level of the parameter type specification are ignored in this fashion; const and volatile type-specifiers buried within a parameter type specification are significant and can be used to distinguish overloaded function declarations. 123 In particular, for any type T, “pointer to T”, “pointer to const T”, and “pointer to volatile T” are considered distinct parameter types, as are “reference to T”, “reference to const T”, and “reference to volatile T”. Сухая выдержка по-русски: Параметры, которые отличаются только const/volatile спецификаторами, считаются эквивалентными. При этом только спецификаторы на внешнем уровне игнорируются. Спецификаторы скрытые внутри типа существенны, и могут быть использованы для разрешения перегрузки.

Ответ 4



Для указателей квалификатор верхнего уровня const точно также отбрасывается и не учитывается. Например, данные два объявления в демонстрационной программе объявляют одну и ту же функцию #include void f( int * ); void f( int * const ); void f( int *p ) { std::cout << "*p = " << *p << std::endl; } typedef int *Ptr; int main() { int x = 10; int *p1 = &x; int * const p2 = &x; const Ptr p3 = &x; f( p1 ); f( p2 ); f( p3 ); return 0; } Вывод на консоль: *p = 10 *p = 10 *p = 10 Дело в том, что аргумент в функцию передается по значению. То есть создается копия аргумента. Поэтому не важно, имеет ли исходный объект квалификатор const или нет. Его значение используется лишь для инициализации параметра функции, который является локальной переменной функции. Инициализация параметра для каждого вызова функции выглядит следующим образом int *p = p1; int *p = p2; int *p = p3; Если в функции будет изменено значение параметра, то на исходный аргумент это никак не повлияет. То есть исходный объект, который передавался в качеств аргумента в функцию, останется без изменения независимо от того, имел ли он квалификатор const или нет. Другое дело, когда указатель указывает на константные данные. Тогда нужно различать, можете ли вы менять эти данные, на которые указывает указатель, или не можете. То есть обращаясь к данным через указатель, вы работаете не с копией данных, а с самими данными. Кстати сказать, если вы передаете указатель по ссылке, то есть вы передаете в функцию не копию аргумента, а, фактически, сам аргумент, то уже имеет место перегрузка. Ниже приведенная программа это демонстрирует #include void f( int * & ); void f( int * const & ); void f( int * & ) { std::cout << "int * &" << std::endl; } void f( int * const & ) { std::cout << "int * const &" << std::endl; } typedef int *Ptr; int main() { int x = 10; int *p1 = &x; int * const p2 = &x; const Ptr p3 = &x; f( p1 ); f( p2 ); f( p3 ); return 0; } Ее вывод на консоль: int * & int * const & int * const & Для простоты рассмотрите следующий пример. Пусть имеются два объявления, одно из которых объявляет константный объект int x = 10; const int y = 20; Тогда при использовании функции вида void f( int x ); вы можете в качестве аргумента передавать любой объект, объявленный выше, так как функция будет иметь дело лишь с копией значений этих объектов. Если же у вас объявлена функция как void f( int & ); или void f( int * ); то вы можете использовать в качестве аргумента для этих функций только первый объявленный объект, как, например, f ( x ); f( &x ); Функция может изменить объект, на который ссылается ее параметр, и сам объект является не константным, его можно менять. Но вы не можете в эту функцию передать второй объявленный объект, та как он константный. Его загрузчик программы может вообще разместить в памяти, которая предназначена только для чтения. Поэтому второй объект можно использовать в функциях, у которых параметр ссылается на константные данные, как, например. void f( const int & ); или void f( const int * ); Тогда вы можете записать f ( y ); f( &y ); Присутствие квалификатора const означает обязательство функции не менять тот объект, на который ссылается ее параметр. Также обратите внимание на объявление const int * const Это объявление можно представить как const int ( * const ) Вы можете, например, написать int ( * const p ) = &x; Квалификатор const в скобочках - это квалификатор верхнего уровня. Он означает, что сам указатель константный. Квалификатор вне скобочек - это квалификатор того объекта, на который ссылается указатель. Для ссылок вы не можете написать аналогичным образом: const int & const Ссылки сами по себе не являются константными (хотя для простоты часто говорят константная ссылка, что означает ссылку, ссылающуюся на константный объект). Это объекты, на которые они ссылаются рассматриваются как константные.

Комментариев нет:

Отправить комментарий