8

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

如果我猜我会说 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 的一个奇怪的怪癖

4

5 回答 5

3

代码不合法​​。

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 是BaseClass通过MyClass:operator=直接传入传入的参数来调用复制赋值运算符,而不仅仅是BaseClassj 的子对象。

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;
}
于 2012-02-27T22:20:06.360 回答
1

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

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

现在编译器发现这BaseClass::operator =<MyClass>(MyClass const&)是最好的匹配并实例化它。

于 2012-02-27T21:40:59.790 回答
0

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

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

于 2012-02-27T21:26:32.220 回答
0

编译器将自动生成一个(默认)复制构造函数,MyClass因为未定义。如果您将iandj的类型从MyClassto更改为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 

MyClass通过es 默认复制BaseClass::=<T>操作符MyClass作为类型调用赋值操作MyClass符,这是 bug 还是 MSVC-ism 由 MS 和 C++ 标准决定。

于 2012-02-27T21:27:16.613 回答
0

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

  • Each subobject is assigned in the manner appropriate to its type: — if the subobject is of class type, the copy assignment operator for the class is used (as if by explicit qualification; that is, ignoring any possible virtual overriding functions in more derived classes);

Note that in 12.8/9 the standard defines a user-declared copy assignment operator as " a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&" (emphasis mine).

So according to the standard, the template function

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

Should not be called by the MyClass implicit copy assignment operator. MSVC is acting in a non-standard fashion here. GCC rightly diagnoses this:

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 
于 2012-02-28T06:46:08.077 回答