【问题标题】:What is different about C++ class function parameter syntax when one of them is a struct?当其中一个是结构时,C++ 类函数参数语法有什么不同?
【发布时间】:2020-02-26 02:23:44
【问题描述】:

在编写新的类函数时,我通常可以安全地将函数定义的第一行(从源文件中)复制到类头中以添加声明,使其成为类的一部分。我通常不使用 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:

这是原始问题的最小可重现代码,从 cmets 获得的见解没有任何变化。

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 风格的结构添加为数据成员是个好主意,但主要的收获可能是在其他地方定义一个类来保存该结构所持有的一对数据成员。

【问题讨论】:

  • 该函数似乎不是结构/类的一部分,至少不是您所展示的。虽然你错过了很多 minimal reproducible example
  • 结构在 C++ 中不是一个东西。 struct 关键字只是声明类的另一种方式(C++ 类在许多方面恰好与 C 结构相似)。将关键字 struct 放在类名前面也是符合语法的,但这没有任何作用:它只是为了保持与 C 的兼容性。
  • "例如,如果没有它,我无法实例化一个新的结构局部变量" - 试试吧,你会感到惊讶 :-) freq_pair my_local_instance;
  • @hegel5000 C++ 中的structclass 之间存在细微差别。在struct 中,成员默认为public,在class 中,默认为privatestruct默认继承关系是公共继承class私有继承
  • @hegel5000 对于这个问题,意识到变量声明中的struct 可能也很重要确实做了一些事情:如果名称查找未找到该类型的声明, struct 将(而不是失败)在变量声明的范围内声明一个新类。这可能是这里正在发生的事情,但在 OP 提供更多代码之前无法确定。

标签: c++ struct


【解决方案1】:

关键字 struct 是可选的,通常在 C++ 中不使用,除非定义或(转发)声明类型。

如果你从函数声明的参数列表中删除“struct”,你会得到一个不同的错误,表明类型 freq_pair 没有被声明。

如果您将 freq_pair 的定义移到类中的函数定义之前,它将按预期编译。或者,在函数之前的类中类型的前向声明也可以工作。

这是正常的。

令我惊讶的是您收到的错误消息。它表明由于您在函数声明参数列表中使用关键字 struct ,编译器已将该参数说明符注册为全局范围的 struct freq_pair 的前向声明,该结构从未在您的代码中定义并且与您的嵌套类型不同。

这种形式的前向声明是标准的一部分,如不均匀标记的答案中所描述和记录的参考文献。简而言之,使用 class 或 struct 关键字使其成为“详细类型说明符”并触发此行为。此外,正如该答案中所指出的那样,标准不认为类范围是前向声明类型的合格范围,因此在您的情况下,您最终会得到在全局范围内声明的类型。

【讨论】:

  • 标准确实指定了进入全局范围的不直观的前向声明,请参阅我的答案或例如this question.
  • @uneven_mark 谢谢。我找到了其中的一部分,但在试图理清标准时遗漏了一些。为您的回答 +1。
【解决方案2】:

C++ 是从源代码自上而下编译的。除了在模板中,每次遇到标识符时,都会执行名称查找。这意味着编译器试图在源代码的前一部分中找到标识符的可达声明。

这里你第一次在函数参数里面使用标识符freq_pair

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

因为它没有事先声明,编译器还不知道freq_pair 应该是什么。通常这会导致错误提示 freq_pair 未声明。然而struct 关键字基本上告诉编译器:“freq_pair 是一个类类型,如果你没有找到这样的类类型,那么在这里声明它。

因此freq_pair 将被声明,但问题是究竟在哪里(即在哪个范围中)它将被声明。它可以在TargaImage 中声明为嵌套类(structclass 都引入类,在 C++ 中,两者在类型标识方面没有区别),作为函数的本地类或作为全局类。事实上,后者是这种情况,正如 C++17 标准(草案 N4659)的[basic.scope.pdecl]/7.2 所指定的(类似情况另见this question):

对于一个详细的类型说明符的形式

类键标识符

如果 [...];否则,除了作为友元声明外,标识符在包含该声明的最小命名空间或块范围内声明。

elaborated-type-specifier 是一个使用 struct 或其他 class-key 关键字之一的类型说明符,即与 @987654334 完全相同@。您的函数声明位于类范围内(既不是命名空间范围也不是块范围),因此不能将 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;
};

定义一个嵌套在类TargaImage中的类freq_pair。这与您事先声明的全局类不同

然后我们来定义

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

这里,因为我们定义了一个属于TargaImage 的函数,所以首先在TargaImage 中查找名称freq_pair,我们现在看到了struct freq_pair {...}; 的定义声明了一个嵌套在TargaImage 内的freq_pair,即TargaImage::freq_pair。如果可以找到匹配名称 的类型,则 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 是一个嵌套类,而不是一个数据成员

【讨论】:

    猜你喜欢
    • 2021-12-09
    • 2013-09-21
    • 1970-01-01
    • 2019-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多