Назначение функции указателю на функцию, правильность аргумента const? - PullRequest
16 голосов
/ 09 июня 2019

Сейчас я изучаю основы C ++ и ООП в моем университете.Я не уверен на 100%, как работает указатель функции при назначении им функций.Я обнаружил следующий код:

void mystery7(int a, const double b) { cout << "mystery7" << endl; }
const int mystery8(int a, double b) { cout << "mystery8" << endl; }

int main() {
    void(*p1)(int, double) = mystery7;            /* No error! */
    void(*p2)(int, const double) = mystery7;
    const int(*p3)(int, double) = mystery8;
    const int(*p4)(const int, double) = mystery8; /* No error! */
}

Насколько я понимаю, назначения p2 и p3 хороши, так как типы параметров функций совпадают, а константа верна.Но почему не удается выполнить задания p1 и p4?Разве не должно быть незаконным сопоставлять const double / int с неконстантным double / int?

Ответы [ 3 ]

18 голосов
/ 09 июня 2019

В соответствии со стандартом C ++ (C ++ 17, 16.1 декларации с перегрузкой)

(3.4) - Объявления параметров, отличающиеся только наличием или отсутствием const и / или volatile, эквивалентны.То есть спецификаторы const и volatile для каждого типа параметра игнорируются при определении, какая функция объявляется, определяется или вызывается.

Таким образом, в процессе определения типа функции определительНапример, const для второго параметра объявления функции ниже отбрасывается.

void mystery7(int a, const double b);

, а тип функции - void( int, double ).

Также рассмотрим следующее объявление функции

void f( const int * const p );

Это эквивалентно следующему объявлению

void f( const int * p );

Второй констант делает параметр постоянным (то есть он объявляет сам указатель как постоянный объект, который не может быть переназначен внутри функции).Первый const определяет тип указателя.Это не отбрасывается.

Обратите внимание на то, что хотя в стандарте C ++ используется термин «константная ссылка», сами ссылки не могут быть постоянными в противоположность указателям.Это следующее объявление

int & const x = initializer;

неверно.

Хотя это объявление

int * const x = initializer;

является правильным и объявляет постоянный указатель.

3 голосов
/ 09 июня 2019

Существует специальное правило для аргументов функции, передаваемых по значению.

Хотя const на них будет влиять на их использование внутри функции (для предотвращения несчастных случаев), оно в основном игнорируется в сигнатуре. Это связано с тем, что const сущность объекта, переданного по значению, никак не влияет на исходный скопированный объект на сайте вызова.

Это то, что вы видите.

(Лично я думаю, что это дизайнерское решение было ошибкой; оно запутанное и ненужное! Но это то, что есть. Обратите внимание, что оно происходит из того же отрывка, который молча меняет void foo(T arg[5]); на void foo(T* arg);, так что есть много Черт возьми! Там уже есть, с которыми нам приходится иметь дело!)

Напомним, однако, что это не просто стирает любой const в типе такого аргумента. В int* const указатель равен const, но в int const* (или const int*) указатель не является const, но относится к const. Только первый пример относится к const сущности самого указателя и будет удален.


[dcl.fct]/5 Тип функции определяется с использованием следующих правил. Тип каждого параметра (включая пакеты функциональных параметров) определяется его собственным decl-specier-seq и декларатором. После определения типа каждого параметра любой параметр типа «массив из T» или типа функции T настраивается на «указатель на T». После создания списка типов параметров, любые верхние уровни cv-qualifiers , модифицирующие тип параметра, удаляются при формировании типа функции . Результирующий список преобразованных типов параметров и наличие или отсутствие многоточия или пакета параметров функции - это параметр-тип-списка функций . [Примечание: Это преобразование не влияет на типы параметров. Например, int(*)(const int p, decltype(p)*) и int(*)(int, const int*) - идентичные типы. - примечание конца]

1 голос
/ 10 июня 2019

Существует ситуация, когда добавление или удаление квалификатора const к аргументу функции является серьезной ошибкой.Это происходит, когда вы передаете аргумент по указателю .

Вот простой пример того, что может пойти не так.Этот код не работает в C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// char * strncpy ( char * destination, const char * source, size_t num );

/* Undeclare the macro required by the C standard, to get a function name that
 * we can assign to a pointer:
 */
#undef strncpy

// The correct declaration:
char* (*const fp1)(char*, const char*, size_t) = strncpy;
// Changing const char* to char* will give a warning:
char* (*const fp2)(char*, char*, size_t) = strncpy;
// Adding a const qualifier is actually dangerous:
char* (*const fp3)(const char*, const char*, size_t) = strncpy;

const char* const unmodifiable = "hello, world!";

int main(void)
{
  // This is undefined behavior:
  fp3( unmodifiable, "Whoops!", sizeof(unmodifiable) );

  fputs( unmodifiable, stdout );
  return EXIT_SUCCESS;
}

Проблема здесь с fp3.Это указатель на функцию, которая принимает два const char* аргумента.Однако он указывает на стандартный вызов библиотеки strncpy() ¹, первым аргументом которого является буфер, который он изменяет .Таким образом, fp3( dest, src, length ) имеет тип, который обещает не изменять данные, на которые указывает dest, но затем передает аргументы в strncpy(), который изменяет эти данные!Это возможно только потому, что мы изменили сигнатуру типа функции.

Попытка изменить строковую константу - это неопределенное поведение - мы фактически сказали программе вызывать strncpy( "hello, world!", "Whoops!", sizeof("hello, world!") ) - и на нескольких различных компиляторах, с которыми я тестировал,во время выполнения он молча завершится ошибкой.

Любой современный компилятор C должен разрешить присвоение fp1, но предупреждает вас, что вы стреляете себе в ногу либо с помощью fp2 или fp3.В C ++ строки fp2 и fp3 не будут компилироваться вообще без reinterpret_cast.Добавление явного приведения заставляет компилятор предполагать, что вы знаете, что вы делаете, и заставляет глушить предупреждения, но программа все равно не работает из-за своего неопределенного поведения.

const auto fp2 =
  reinterpret_cast<char*(*)(char*, char*, size_t)>(strncpy);
// Adding a const qualifier is actually dangerous:
const auto fp3 =
  reinterpret_cast<char*(*)(const char*, const char*, size_t)>(strncpy);

Это не происходит с аргументами, передаваемыми по значениюпотому что компилятор делает копии тех.Пометка параметра, переданного значением const, означает, что функция не должна изменять свою временную копию.Например, если стандартная библиотека внутренне объявлена ​​char* strncpy( char* const dest, const char* const src, const size_t n ), она не сможет использовать идиому K & R *dest++ = *src++;.Это изменяет временные копии функции аргументов, которые мы объявили const.Поскольку это не влияет на остальную часть программы, C не возражает, если вы добавите или удалите квалификатор const, подобный описанному в прототипе функции или указателе функции.Обычно вы не делаете их частью общедоступного интерфейса в заголовочном файле, так как они являются деталями реализации.

¹ Хотя я использую strncpy() в качестве примера известной функции справильная подпись, она вообще устарела.

...