为什么即使我有未定义的成员函数,下面的代码也会编译?

时间:2012-02-27 21:19:57

标签: c++ sfinae

我正在处理这段代码,并且在点击构建按钮之前想到这显然不会编译。我很惊讶它不仅编译,而且链接和工作。

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

5 个答案:

答案 0 :(得分:3)

代码不合法​​。

i = jMyClass中调用隐式定义的复制赋值运算符。此函数为其每个子对象调用复制赋值运算符,包括直接基类[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=复制赋值运算符,而不仅仅是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;
}

答案 1 :(得分:1)

在黑暗中拍摄:编译器使用operator = = T实例化基类“MyClass”。我现在知道这是合法的还是必需的但它有一定意义:operator =的自动生成代码基本上看起来像这样(好吧,伪代码):

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

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

答案 2 :(得分:0)

好吧,因为它无法调用BaseClass::operator=(const BaseClass&)(来自默认生成的MyClass::operator=因为它是私有的),因为它是私有的,它只是用template<typename T> BaseClass::operator(const T &)调用T=BaseClass。所以没有调用非定义函数。

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

答案 3 :(得分:0)

编译器将自动生成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 

MyClass BaseClass::=<T>使用MyClass作为类型通过MyClass默认复制运算符调用{{1}}赋值运算符,无论这是错误还是MSVC-ism由MS和C ++标准决定。

答案 4 :(得分:0)

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

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

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

所以根据标准,模板功能

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

MyClass隐式复制赋值运算符不应该调用它。 MSVC在这里以非标准方式行事。海湾合作委员会正确诊断了这一点:

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