Чем отличается синтаксис параметра функции класса C ++, если один из них является структурой? - PullRequest
2 голосов
/ 30 октября 2019

При написании новой функции класса для меня обычно безопасно скопировать первую строку определения функции (из исходного файла) в заголовок класса, чтобы добавить объявление, чтобы оно было частью класса. Я обычно не использую структуры в C ++, но теперь, когда я это сделал, я столкнулся со случаем, когда кажется, что есть какой-то особый случай с указанием структуры в списке параметров в определении. Структура является членом данных того же класса.

Определение функции в заголовочном файле:

int freq_sort(unsigned char* source, struct freq_pair* target);

Первая строка этого определения функции в исходном файле:

int TargaImage::freq_sort(unsigned char* source, struct freq_pair* target){

Ошибка компилятора в объявлении:

TargaImage.cpp:324:5: error: no declaration matches ‘int TargaImage::freq_sort(unsigned char*, TargaImage::freq_pair*)’
  324 | int TargaImage::freq_sort(unsigned char* source, struct freq_pair* target){
      |     ^~~~~~~~~~

Ошибка компилятора, предлагающая правильное определение:

TargaImage.h:96:13: note: candidate is: ‘int TargaImage::freq_sort(unsigned char*, freq_pair*)’
   96 |         int freq_sort(unsigned char* source, struct freq_pair* target);
      |             ^~~~~~~~~

Элемент данных структуры:

        struct freq_pair {
            unsigned char val;
            int count;
        };

Я вижу ключевое отличиекомпилятор видит оператор разрешения контекста, используемый с параметром struct в определении, но не в объявлении. Я не понимаю, почему это так. Что здесь происходит?

Я пытался добавить область видимости класса к параметру struct в определении, объявлении или в обоих, чтобы заставить его работать, но безрезультатно. Даже если это произойдет, я не пойму, в чем проблема.

Каков мой следующий шаг к поиску проблемы здесь?

Спасибо,
Трент

РЕДАКТИРОВАТЬ 1:

Ниже приведен минимальный воспроизводимый код исходной задачи без изменений, полученных в комментариях.

class TargaImage
{
  public:
    //function
    int freq_sort(unsigned char* source, struct freq_pair* target);

    //data member 
    struct freq_pair {
       unsigned char val;
       int count;
    };
};

int TargaImage::freq_sort(unsigned char* source, struct freq_pair* target){
  return 0;  
}

int main(){
  TargaImage obj;
  return 0;
}

Теперь я понимаю, что структуры воспринимаются как классы. Итак, если я по сути определяю новый «класс» (структуру) внутри моего исходного класса, правила включения этого класса для использования должны отличаться от обычной директивы препроцессора. Я думаю, что поскольку новый "класс" (структура) freq_pair находится внутри исходного класса TargaImage, его определение находится в рамках функции-члена TargaImage. Итак, удалите ключевое слово struct из параметров. Но я получаю сообщение об ошибке:

main.cpp:6:42: error: ‘freq_pair’ has not been declared
    6 |     int freq_sort(unsigned char* source, freq_pair* target);
      |   

Итак, я пришел к выводу, что freq_pair находится вне области действия TargaImage, хотя оно определено внутри класса. Это заключение правильно? Как мне сообщить TargaImage :: freq_sort (...) о типе класса freq_pair?

Я подумал, что добавление структуры стиля C в качестве члена данных было бы хорошей идеей, но главное, что можно сделать, это определить класс в другом месте, который содержит пару элементов данных, которые содержала структура.

Ответы [ 2 ]

3 голосов
/ 31 октября 2019

C ++ компилируется сверху вниз в исходном коде. За исключением шаблонов, каждый раз, когда встречается идентификатор, выполняется поиск имени . Это означает, что компилятор пытается найти достижимое объявление идентификатора в предыдущей части исходного кода.

Здесь вы впервые используете идентификатор freq_pair в параметре функции внутри

int freq_sort(unsigned char* source, struct freq_pair* target);

Поскольку он не был объявлен заранее, компилятор еще не знает, каким должен быть freq_pair. Обычно это приводит к ошибке, говорящей о том, что freq_pair не объявлено. Однако ключевое слово struct в основном говорит компилятору: "freq_pair является типом класса, и если вы не найдете такого типа класса, объявите его здесь. "

Поэтомуfreq_pair будет объявлено, но вопрос будет , где именно (т. Е. В котором scope ) будет объявлено. Он может быть объявлен как вложенный класс (struct и class оба представляют классы, в C ++ нет различия между этими двумя в отношении идентичности типов) внутри TargaImage, как локальный класс для функции или какглобальный класс. На самом деле последний случай имеет место, поскольку [basic.scope.pdecl] /7.2 стандарта C ++ 17 (черновик N4659) определяет (см. Также этот вопрос для аналогичногоcase):

для подробного спецификатора типа формы

идентификатор ключа класса

if [...];в противном случае, за исключением объявления друга, идентификатор объявляется в наименьшем пространстве имен или области блока, в которой содержится объявление.

подробный спецификатор типа - это спецификатор типа, которыйиспользует struct или одно из других ключевых слов class-key , то есть именно то, что вы используете с struct freq_pair. Объявление вашей функции находится внутри области видимости класса (которая не является ни областью имен, ни областью блоков), поэтому объявление freq_pair не может быть размещено там. Следующая наименьшая область, содержащая class TargaImage {...}; - это глобальная область, которая равна областью имен. Таким образом, строка

int freq_sort(unsigned char* source, struct freq_pair* target);

объявляет глобальный struct freq_pair, и тип, указанный в объявлении, является этим типом.

Тогда

struct freq_pair {
   unsigned char val;
   int count;
};

определяет класс freq_pair вложено в класс TargaImage. Это не тот же класс , что и глобальный класс, который вы объявили заранее.

Затем мы приходим к определению

int TargaImage::freq_sort(unsigned char* source, struct freq_pair* target){
    return 0;  
}

Здесь, потому что мы определяем функцию, котораяявляется частью TargaImage, имя freq_pair сначала ищется внутри TargaImage, где мы сейчас видим определение struct freq_pair {...};, в котором объявлено freq_pair, вложенное в TargaImage, т.е. 1081 *. Ключевое слово struct не имеет никакого дальнейшего эффекта, если тип, соответствующий имени , может быть найден , поэтому freq_pair в этом определении теперь ссылается на TargaImage::freq_pair.

В результатеВы объявили функцию-член, принимающую указатель на глобальный ::freq_pair, но попытались определить функцию-член, принимающую указатель на вложенную ::TargaImage::freq_pair. Компилятор жалуется, что они не совпадают.


Чтобы решить эту проблему, удалите все ключевые слова struct в объявлениях переменных и используйте их только для определения или явного объявления классов вперед. Как видите, использование его в качестве детализированного спецификатора типа вызывает только головные боли. Это же правило применяется и к другим разработанным спецификаторам типов, т. Е. К тем, которые начинаются с class / enum / union.

Однако это приведет к ошибке, поскольку freq_pair не найден вдекларация члена, как я объяснил выше. Это легко решается перемещением определения freq_pair перед точкой использования:

class TargaImage
{
public:
    //data member 
    struct freq_pair {
       unsigned char val;
       int count;
    };

    //function
    int freq_sort(unsigned char* source, freq_pair* target);
};

int TargaImage::freq_sort(unsigned char* source, freq_pair* target){
  return 0;  
}

Если это невозможно по какой-либо причине, то вы можете использовать предварительное объявление, чтобы убедиться, что первый поискнаходит правильный тип (даже если он неполный в этой точке):

class TargaImage
{
  public:

    //explicit forward declaration
    struct freq_pair;

    //function
    int freq_sort(unsigned char* source, freq_pair* target);

    //data member 
    struct freq_pair {
       unsigned char val;
       int count;
    };
};

int TargaImage::freq_sort(unsigned char* source, freq_pair* target){
  return 0;  
}

Также обратите внимание, что freq_pair является вложенным классом ,не элемент данных .

2 голосов
/ 30 октября 2019

Ключевое слово struct является необязательным и обычно не используется в C ++, за исключением случаев определения или (прямого) объявления типа.

Если вы удалите «struct» из списка параметров в объявлении функции, вы будетеполучить другую ошибку, указывающую на то, что тип freq_pair не был объявлен.

Если вы переместите определение freq_pair до определения функции в классе, оно будет скомпилировано должным образом. Кроме того, предварительное объявление типа в классе перед функцией также будет работать.

Это нормально.

Что меня здесь удивило, так это сообщение об ошибке, которое вы получили. Это указало на то, что из-за того, что вы использовали ключевое слово struct в списке параметров объявления функции, компилятор зарегистрировал этот спецификатор параметра как предварительное объявление глобально структурированной структуры freq_pair, которая никогда не определяется в вашем коде и отличается от вашего вложенного типа.

Эта форма предварительного заявления является частью стандарта, как описано и задокументировано со ссылками в ответе unsven_mark. Короче говоря, использование ключевого слова class или struct делает это «детализированным спецификатором типа» и запускает такое поведение. Кроме того, как указано в этом ответе, область действия класса не рассматривается в стандарте как приемлемая область видимости для типа, объявленного вперед, поэтому в вашем случае вы получите тип, объявленный в глобальной области видимости.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...