【问题标题】:Why does the following code compile even though I have undefined member functions?为什么即使我有未定义的成员函数,以下代码也会编译?
【发布时间】:2012-02-27 21:19:57
【问题描述】:

我正在编写这段代码的一半,并在点击构建按钮之前认为这显然不会编译。我很惊讶它不仅可以编译,而且可以链接和工作。

如果我猜我会说 SFINAE 负责它的编译...是吗?

struct BaseClass
{
public:
  BaseClass() {}

  template<typename T>
  BaseClass(const T& a_other)
  {
    int i = 0; // for break point
  }

  template<typename T>
  BaseClass& operator= (const T& a_other)
  {
    int i = 0; // for break point
    return *this;
  }

private:

  BaseClass(const BaseClass& a_other); // Does not have a definition
  BaseClass& operator= (const BaseClass& a_other); // Does not have a definition

};

struct MyClass : public BaseClass
{
};

int main()
{
  MyClass i, j;
  i = j;

  return 0;
}

编辑:我使用的是 Visual-C++ 2008,可能是 VS 的一个奇怪的怪癖

【问题讨论】:

  • 您具体问的是哪一部分?有模板函数,还有未定义的复制和赋值运算符,我可以想象有人期待某种失败。
  • @GregHewgill: Line i = j ... 不应该简单地告诉我赋值运算符是私有的吗?
  • 我认为这个问题是相关的:stackoverflow.com/questions/8984013/….
  • 这不能在 gcc 4.6.1 下编译,启用 C++11:错误说:在函数 'int main()' ... 使用已删除的函数 'MyClass& MyClass ::operator=(const MyClass&) MyClass& MyClass::operator=(const MyClass&)' 被隐式删除,因为默认定义格式错误:| BaseClass& BaseClass::operator=(const BaseClass&)' 是私有的|
  • 它不能编译。如果是这样,那就是一个 VC++ 错误。

标签: c++ sfinae


【解决方案1】:

代码不合法​​。

i = j 调用MyClass 中隐式定义的复制赋值运算符。此函数为其每个子对象调用复制赋值运算符,包括直接基类 [class.copy 12.8 p28]。

如果你将代码添加到 BaseClass 的复制赋值运算符中,你可以看到 VS 哪里出错了:

  template<typename T>
  BaseClass& operator= (const T& a_other)
  {
    std::cout << typeid(T).name() << '\n';
    int i = 0; // for break point
    return *this;
  }

对我来说,这会打印出“struct MyClass”。 VS 通过直接传递MyClass:operator= 中接收的参数来调用BaseClass 复制赋值运算符,而不仅仅是j 的BaseClass 子对象。

SFINAE 没有发挥作用,因为模板函数没有失败。 VS 只是错误地生成了隐式复制赋值运算符。

总结一下: VS 将隐式复制赋值运算符生成为

MyClass &operator=(const MyClass& rhs) {
    static_cast<BaseClass&>(*this).operator=(rhs);
    return *this;
}

什么时候应该:

MyClass &operator=(const MyClass& rhs) {
    static_cast<BaseClass&>(*this).operator=(static_cast<const BaseClass&>(rhs));
    return *this;
}

【讨论】:

【解决方案2】:

在黑暗中射击:编译器将基类 operator = 实例化为 T = MyClass。我现在知道这是否合法甚至是必需的,但它具有某种意义:operator = 的自动生成代码基本上看起来像这样(嗯,伪代码):

MyClass& operator =(MyClass const& other) {
    BaseClass::operator =(other);
    return *this;
}

现在编译器发现 BaseClass::operator =&lt;MyClass&gt;(MyClass const&amp;) 是最佳匹配并实例化它。

【讨论】:

  • 我也是这么想的。但我不确定这是正确的行为。
  • @Christian 鉴于 GCC 不编译这个,我敢说它实际上是错误的。但我不确定。而且该标准足够复杂,以至于不明显。
【解决方案3】:

好吧,因为它是私有的,所以它不能调用BaseClass::operator=(const BaseClass&amp;)(从默认生成的MyClass::operator= 来表示它的价值),它只能用T=BaseClass 调用template&lt;typename T&gt; BaseClass::operator(const T &amp;)。所以没有调用未定义的函数。

我猜如果其他人是公开的,情况会有所不同,在这种情况下,编译器会更喜欢那些而不是模板,但是由于他在私有时看不到它们,所以模板版本同样匹配。

【讨论】:

  • 但是名称解析不是在之前考虑公共/私有吗? (这是一个真正的问题;我以为我在某个地方读到过。)
  • 那么在这种情况下,它遵循的是 SFINAE 技术吗?
  • @Samaursa 我不认为 SFINAE(至少使用我知道的术语)与此有关。只是因为编译器看不到/使用私有版本,但它们仍然存在,只是没有被调用。
  • @OliCharlesworth 不知道,也许也会这么想。但这是我能想到的唯一解释。其他答案根本没有真正解决问题。
  • 我明白了...我认为它可能是 SFINAE 的原因是因为非模板方法应该优先于模板方法,不是吗?如果是,那么编译器应该编译它并报告我正在尝试访问私有方法。
【解决方案4】:

编译器将自动为MyClass 生成一个(默认)复制构造函数,因为没有定义。如果您将ij 的类型从MyClass 更改为BaseClass,您将看到您预期的错误,因为编译器会尝试绑定私有的、未实现的赋值运算符。


使用 MSVC 2010 Ultimate SP1 更深入地研究这一点,我们可以看到确切的原因:

MyClass::operator=:
00201230  push        ebp  
00201231  mov         ebp,esp  
00201233  push        ecx  
00201234  mov         dword ptr [ebp-4],ecx  
00201237  mov         eax,dword ptr [__that]  
0020123A  push        eax  
0020123B  mov         ecx,dword ptr [this]  
0020123E  call        BaseClass::operator=<MyClass> (202130h)  
00201243  mov         eax,dword ptr [this]  
00201246  mov         esp,ebp  
00201248  pop         ebp  
00201249  ret         4 

BaseClass::=&lt;T&gt; 使用 MyClass 作为类型通过 MyClasses 默认复制运算符调用 MyClass 赋值运算符,无论这是错误还是 MSVC 主义取决于 MS 和 C++ 标准做出决定。

【讨论】:

  • 但我可以闯入BaseClassoperator=()(因此注释for break point),至少在Visual-C++ 中是这样。如果它生成一个默认值,那么为什么要调用基本赋值运算符?
  • 但是默认生成的复制构造函数调用基类复制构造函数...
  • @Samaursa:如果你真的追踪到你设置的断点,你会看到它返回到自动生成的函数MyClass::operator=:(至少这是我在 MSVC 2010 下得到的)。
  • 是的,我看到了同样的行为。但是它 is 去基类重载而不是给出错误,因为我正在尝试访问私有赋值运算符(应该首先选择私有运算符和构造函数,因为它们是非模板版本)。
  • @Samaursa:编译器选择了它唯一可见的选项,即公共模板赋值运算符。
【解决方案5】:

MyClass 将使用隐式定义的复制赋值运算符,因为没有用户声明的版本。根据标准,隐式复制分配将执行以下操作(C++03 12.8/13“复制类对象”):

  • 每个子对象都以适合其类型的方式分配: — 如果子对象是类类型,则使用该类的复制赋值运算符(好像通过显式限定;也就是说,忽略更多派生类中任何可能的虚拟覆盖函数);

请注意,在 12.8/9 中,该标准将用户声明的复制赋值运算符定义为“ X 类的非静态 非模板 成员函数,只有一个 X 类型的参数,X&, const X&、volatile X& 或 const volatile X&"(强调我的)。

所以按照标准,模板函数

  template<typename T>
  BaseClass& operator= (const T& a_other);

不应由MyClass 隐式复制赋值运算符调用。 MSVC 在这里以非标准方式行事。 GCC 正确地诊断了这一点:

C:\temp\test.cpp: In member function 'MyClass& MyClass::operator=(const MyClass&)':
C:\temp\test.cpp:29:14: error: 'BaseClass& BaseClass::operator=(const BaseClass&)' is private
C:\temp\test.cpp:33:8: error: within this context
C:\temp\test.cpp: In function 'int main()':
C:\temp\test.cpp:40:7: note: synthesized method 'MyClass& MyClass::operator=(const MyClass&)' first required here 

【讨论】:

    猜你喜欢
    • 2017-01-03
    • 2022-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-24
    • 1970-01-01
    • 2013-08-30
    • 1970-01-01
    相关资源
    最近更新 更多