我正在处理这段代码,并且在点击构建按钮之前想到这显然不会编译。我很惊讶它不仅编译,而且链接和工作。
如果我猜我会说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奇怪的怪癖
答案 0 :(得分: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=
复制赋值运算符,而不仅仅是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
的(默认)复制构造函数,因为未定义一个。如果您将i
和j
的类型从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