C ++:使用模板时不覆盖函数

时间:2012-11-01 19:54:42

标签: c++ oop templates linker override

我正处于为学校作业编写一个小型日历库的最后阶段,我遇到了一个意想不到的,非常令人困惑的问题;我引入模板时,我的赋值运算符没有被覆盖!

所以结构如下:我有一个抽象类Date,主要是纯虚函数(包括赋值运算符),然后我有两个子类GregorianJulian实现其所有功能。最后,我为类Calendar编写了一个模板,其中包含今天的日期(以GregorianJulian对象的形式)以及其他一些与之无关的内容这个特殊的问题。

问题在于,当尝试设置此成员today时,我收到一个很长的链接器错误:

  

错误4错误LNK2019:未解析的外部符号“public:virtual class lab2 :: Date& __thiscall lab2 :: Date :: operator =(class lab2 :: Date const&)”(?? 4Date @ lab2 @@ UAEAAV01 @ ABV01 @@ Z)在函数“public:class lab2 :: Gregorian& __thiscall lab2 :: Gregorian :: operator =(class lab2 :: Gregorian const&)”中引用“(?? 4Gregorian @ lab2 @@ QAEAAV01 @ ABV01 @@ Z)C:\ Users ... \ test.obj Calendar

告诉我它在operator=类中找不到函数Date(显然是因为它纯粹是虚拟的)。为什么不使用任何被覆盖的? 它告诉我Gregorian::operator=正在尝试拨打Date::operator=

以下是出现问题的简化代码:

namespace cal_lib {
    template <typename T>
    class Calendar {
    public:
        Calendar() {
            today = T(); // this yields the error
        }
    private:
        T today;
    };
 }

这是来自 Gregorian.cpp 的片段:

namespace cal_lib {
    class Gregorian : public Date {
    public:
        Gregorian();
        virtual Gregorian& operator=(const Date& date);
        virtual Date& add_year(int n = 1);
        virtual Date& add_month(int n = 1);
    };

    // here I'm using Date's constructor to get the current date
    Gregorian::Gregorian() {}

    Gregorian& Gregorian::operator=(const Date& date) {
        if (this != &date) {
            // these member variables are specified as
            // protected in Date
            m_year = 1858;
            m_month = 11;
            m_day = 17;

            add_year(date.mod_julian_day()/365);
            add_month((date.mod_julian_day() - mod_julian_day())/31);
            operator+=(date.mod_julian_day() - mod_julian_day());
        }
    }
}

Date的(默认)构造函数只是将m_yearm_monthm_day的值设置为今天的日期:

Date::Date() {
    time_t t;
    time(&t);
    struct tm* now = gmtime(&t);
    m_year = now->tm_year + 1900;
    m_month = now->tm_mon + 1;
    m_day = now->tm_mday;
}

值得注意的是,这完全正常:

Gregorian g;
Julian j;
g = j; // no problems here

Date* gp = new Gregorian();
Date* jp = new Julian();
*gp = *jp; // no problems here either

这是实例化Calendar类的方法:

using namespace cal_lib;

Calendar<Gregorian> cal;

我在这里有一些非常明显的错误吗?

4 个答案:

答案 0 :(得分:5)

如果您仔细阅读错误消息,您会注意到两件事,编译器正试图找到以下定义:

Date& operator=(const Date&)

根据以下定义需要符号:

Gregorian& operator=(const Gregorian&)

*那么该运营商甚至如何出现在您的计划中? *

复制构造函数和赋值运算符是特殊的,它们将始终在程序中声明。您要么提供声明,要么编译器会为您执行此操作。您已提供Gregorian& Gregorian::operator=(const Date&),但不会从该计划中删除Gregorian& Gregorian::operator=(const Gregorian&)

当您尝试将一个Gregorian对象分配给另一个时,编译器将发现您的两个重载和隐式定义,并且重载解析将发现隐式声明的赋值是更好的匹配。这将以类似于:

的方式触发赋值运算符的定义
T& operator=( T const & o ) {
   Base::operator=(o);           // for all bases
   member=o.member;              // for all members
}

您可以采取不同的措施来解决此问题。最简单的可能是在程序中定义Date Date::operator=(const Date&)(将其保留为纯虚函数,但也提供定义)。这样,当编译器遇到两个具有相同派生类型的对象时,它可以发挥其魔力,一切都会起作用。您也可以使用它作为通过强制派生类型中的派遣来分解基础成员副本的实现的方法。

另一个需要付出更多努力的选择是实际声明并定义所有派生类型的赋值运算符。这里要编写更多的代码,并且需要复制处理Date成员的代码,如果修改基类,则需要更新所有派生赋值运算符...我会不要走那条路。

最后,一旦修复了编译器错误,请考虑对于一般Date(实际上可能是Gregorian日期),您对赋值运算符的实现是否有意义。考虑如果你这样做会发生什么:

Gregorian d1 = ...; // some date
Gregorian d2 = d1;  // same date
Date &d = d2;
d1 = d2;            // What date does 'd1' represent??

请注意,如果您提供Date::operator=的定义并让编译器为您生成Gregorian::operator=(Gregorian const&),则此示例中的问题就会消失。

答案 1 :(得分:2)

首先:当你的Date& Date::operator=(const Date&)类中有公共Date时,编译器会为你的Gregorian& Gregorian::operator=(const Gregorian&)类生成隐式函数Gregorian

编译器生成的函数的行为类似于:

Gregorian& Gregorian::operator=(const Gregorian& g)
{
    Date::operator=(g);
    return *this;
}

根据重载决策规则,如果

,此函数为更好的macth
Gregorian g;
Gregorian u;
g = u;

然后Date& Date::operator=(const Date&)

如果你delete这个函数或私有,它仍然会被声明并且更适合编译器,因为编译器在选择重载时忽略了可访问性问题:

  

[注意:不保证重载决议选择的功能   适合上下文。其他限制,如   函数的可访问性,可以在调用上下文中使用它   病态的。 -end note ]

13.3重载决议,C ++ 11标准草案

你可能应该为Date operator=函数编写实现。对于所有日历实现,您可能应该具有相同的下划线数据格式,那么您将不需要虚拟运算符=。现在您正在尝试执行某些转换,但要正确执行此操作,您不仅要知道 left operator=操作数的类型,还要知道 right 操作数的类型还

答案 2 :(得分:1)

说明问题的简化代码:

class A {
public:
  virtual A& operator = (const A& a) = 0;
};

class B : public A {
public:
  virtual B& operator = (const A&)
  {
      return *this;
  }
};

int main() 
{
  B b;
  b = B();
}

http://liveworkspace.org/code/add55d1a690d34b9b7f70d196d17657f

Compilation finished with errors:
/tmp/ccdbuBWe.o: In function `B::operator=(B const&)':
source.cpp.cpp:(.text._ZN1BaSERKS_[_ZN1BaSERKS_]+0x14): undefined reference to `A::operator=(A const&)'
collect2: error: ld returned 1 exit status

你认为你称之为

virtual B& B::operator = (const A&)
//                              ^

但实际上所谓的是自动生成的默认运算符:

  virtual B& B::operator = (const B&)
   //                             ^

它的默认实现调用此运算符:

  virtual A& operator = (const A& a) = 0;

未实现 - 所以错误。

最简单的解决方案是在作业中进行显式转换:

int main() 
{
  B b;
  b = static_cast<const A&>(B());
}

我建议 - 不要将A运算符定义为虚拟 - 这在这里没有意义。

如果你真的想要它是虚拟的,那么要么实现它的基本纯虚拟版本:

class A {
public:
  virtual A& operator = (const A& a) = 0;
};
inline A& operator = (const A& a) { return *this; }

或者 - 从A类派生的所有内容中删除默认operator = - 这样做非常不方便......

答案 3 :(得分:0)

  

(显然因为它纯粹是虚拟的)。

你确定吗?如何定义完全?我认为你的定义错了。 它应该看起来像

virtual Date& operator=(const Date& r) = 0;

请注意返回值和const Date&