【问题标题】:C++: pure virtual assignment operatorC++:纯虚赋值运算符
【发布时间】:2011-04-15 05:32:34
【问题描述】:

为什么如果我们在基类中有纯虚拟赋值运算符,那么我们在派生类上实现该运算符,它会在基类上给出链接器错误?

目前我对http://support.microsoft.com/kb/130486只有以下解释,它说这种行为是设计使然,因为正常的继承规则不适用

我不清楚,为什么设计会产生链接器错误?有人可以给我更清楚的解释吗?

编辑:添加了发生错误的我的简化代码

class __declspec(dllexport) BaseClass {
public:
    int memberA;
    virtual BaseClass& operator=(const BaseClass& rhs) = 0;
};

class __declspec(dllexport) DerivedClass : public BaseClass {
public:
    int memberB;
    DerivedClass():memberB(0) {}
    virtual BaseClass& operator=(const BaseClass& rhs) {
        this->memberA = rhs.memberA;
        this->memberB = 1;
        return *this;
    }
};

int main(void)
{
    DerivedClass d1;
    DerivedClass d2;

    BaseClass* bd1 = &d1;
    BaseClass* bd2 = &d2;

    *bd1 = *bd2;
}

代码编译时不会出错没有 __declspec(dllexport) 和/或没有基类上的纯虚拟运算符= 声明。

在分配*bd1 = *bd2; 后没有__declspec(dllexport),d1::memberB 为1,但__declspec(dllexport) d1::memberB 保持不变

__declspec(dllexport),并且没有纯虚声明,分配*bd1 = *bd2;后,d1::memberB 保持不变

【问题讨论】:

  • 可能有助于发布一些示例代码以及 MSVC 正在吐出的完整链接器错误
  • FWIW,Scott Meyers 的 More Effective C++(“Make Non-Leaf Classes Abstract”)第 33 项介绍了如何在继承层次结构中处理 operator=。跨度>
  • @necrolis:示例代码在上面的给定链接上
  • @uray:理想情况下,关于 SO 的问题将是独立的,因为一个目的是为那些追随我们的人建立一个知识库。我已经看到 microsoft.com 上的内容随着时间的推移而消失。
  • @uray:除了 David Thornley 提到的之外,有时 MS 代码本身可能包含错误,或者只是太笼统以至于不能很好地适用于您的具体情况。我推断该示例来自您的代码,这可能有一个错误 MS 没有。

标签: c++ linker-errors operator-keyword pure-virtual


【解决方案1】:

operator= 没有被继承。您的代码在 C++ 中毫无意义,因此编译器可以随意发出任何他们想要的错误。

来自您指向的知识库文章:http://support.microsoft.com/kb/130486

由于 operator= 不是继承的,因此基类中的任何 operator= 声明都是未使用且不必要的。不要在基类中声明 operator=。

这可能只是他们编译方式的副作用,他们只是让你知道他们不认为这是一个错误,所以没有必要修复它。 “按设计”并不一定意味着他们明确认为这个链接器错误是针对这种情况给出的正确错误消息——代码错误,你得到一个错误,所以从他们的角度来看——他们是完成。

【讨论】:

  • OP 的知识库文章。我会更新
【解决方案2】:

在示例代码中:

class A
{
public :
   // To workaround LNK2001, comment the following line.
   virtual const A& operator=( const A& f ) = 0;
};

class B : public A
{
public :
   const A& operator=( const A& g ) {return g;}
};

B aB1, aB2;

int /*void*/ main( void )
{
   aB2 = aB1;
}

aB2 = aB1 行不调用const A& B::operator=(const A&),而是调用自动提供的B& operator=(const B&);,而B& operator=(const B&); 又使用赋值运算符来分配类的基部分。但是当涉及到链接时,事实证明这从未实现过。

【讨论】:

  • 赋值运算符已被覆盖。就像声明任何构造函数(非模板)会有效地停用隐式定义的默认构造函数和复制构造函数一样,声明任何 operator=(非模板)会停用隐式定义的赋值运算符。
  • @Matthieu:这是否符合复制分配的条件?毕竟,struct X { X& operator=(int);}; 不会阻止我将一个 X 分配给另一个它应该分配的 X,如果 operator=any 重载禁用了默认值。 - 同样,任何构造函数都不会停用复制构造函数,只有有效的用户定义的复制构造函数。
  • 你是对的,但是当使用B& operator=(A const&) 时,这个版本被调用而不是默认版本(至少在 VC++10 中)......现在我很困惑,为时已晚我来调查一下……
【解决方案3】:

来自标准的第 12.8 节:

13 为类 X 隐式定义的复制赋值运算符执行其子对象的成员赋值。 X 的直接基类首先按照它们在基说明符列表中的声明顺序分配,然后是直接基类 X 的非静态数据成员按照它们在类定义中声明的顺序进行分配。每个子对象 以适合其类型的方式分配:

——如果子对象是类类型,则使用该类的复制赋值运算符(如同通过显式限定; 也就是说,忽略更多派生类中任何可能的虚拟覆盖函数);

子类使用了隐式定义的复制赋值操作符,基类的复制赋值操作符没有定义,但是声明了,所以你得到的是链接错误而不是编译错误。

【讨论】:

  • 我认为他在谈论 operator=,而不是复制构造函数。对于复制 ctor -- virtual 和 pure virtual 应该是一个直接的编译器错误,因为这对他们来说真的没有意义。
  • 他的链接是关于 operator= 的,这就是我的帖子的内容。
  • 来自 OP then we implement that operator on the derived class 我要说他在孩子中有一个明确的复制赋值运算符(不是隐含的,你的答案会正确涵盖),并且没有迹象表明它是或不是'不调用父(纯虚拟)复制分配。
  • 没错,但在 cmets 中,他确实明确地将 Necrolis 指向了链接。即使如此,如果他在 B 中定义 A &operator= (A&),赋值仍然会调用隐式版本,因为两个操作数都是 B 类型,并且“基类复制赋值运算符总是被派生类的复制赋值运算符隐藏” (12.8.10)。
猜你喜欢
  • 1970-01-01
  • 2010-10-14
  • 1970-01-01
  • 2011-07-29
  • 1970-01-01
  • 2017-12-10
  • 2021-08-10
  • 2016-10-01
  • 2016-04-01
相关资源
最近更新 更多