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

hag*_*ard 8 c++ oop linker templates overriding

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

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

问题是,当尝试设置此成员时,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日历

告诉我它无法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;
    };
 }
Run Code Online (Sandbox Code Playgroud)

这是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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

日期(默认)构造函数只是设置的值m_year,m_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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

Date* gp = new Gregorian();
Date* jp = new Julian();
*gp = *jp; // no problems here either
Run Code Online (Sandbox Code Playgroud)

Calendar是实例化类的方式:

using namespace cal_lib;

Calendar<Gregorian> cal;
Run Code Online (Sandbox Code Playgroud)

这里有一些非常明显的错误,我在做什么?

Dav*_*eas 5

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

Date& operator=(const Date&)
Run Code Online (Sandbox Code Playgroud)

从以下定义中需要符号:

Gregorian& operator=(const Gregorian&)
Run Code Online (Sandbox Code Playgroud)

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

复制构造函数和赋值运算符是特殊的,它们将始终在程序中声明.您要么提供声明,要么编译器会为您执行此操作.您提供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
}
Run Code Online (Sandbox Code Playgroud)

您可以采取不同的方法来解决此问题.最简单的可能是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??
Run Code Online (Sandbox Code Playgroud)

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