8

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

所以结构如下:我有一个抽象类Date,其中大部分是纯虚函数(包括赋值运算符),然后我有两个子类GregorianJulian实现了它的所有功能。最后,我为 class 编写了一个模板Calendar,其中包含今天的日期(以 aGregorianJulian对象的形式)和一些与这个特定问题并不真正相关的其他内容。

问题是当试图设置这个成员时,today我得到一个长链接器错误:

错误 4 error LNK2019: unresolved external symbol "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;
    };
 }

这是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

4 回答 4

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&)

于 2012-11-01T21:42:55.720 回答
2

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

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

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

根据重载解析规则,这个函数在万一情况下比较好

Gregorian g;
Gregorian u;
g = u;

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

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

[注意:重载决议选择的函数不能保证适合上下文。其他限制,例如函数的可访问性,可能使其在调用上下文中的使用格式错误。——尾注]

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

operator=您可能应该为 Date函数编写实现。对于所有日历实现,您可能应该具有相同的下划线数据格式,那么您就不需要 virtual operator=。现在您正在尝试执行一些转换,但要正确执行,您不仅必须知道 operator=操作数的类型,还必须知道操作数的类型。

于 2012-11-01T21:43:34.150 回答
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 =- 这非常不方便......

于 2012-11-01T21:49:49.557 回答
0

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

你确定吗?它是如何准确定义的?我认为你的定义是错误的。它应该看起来像

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

注意返回值和const Date&

于 2012-11-01T19:58:35.910 回答