【问题标题】:C++: Function not overridden when using templatesC ++:使用模板时未覆盖函数
【发布时间】:2012-10-22 11:30:09
【问题描述】:

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

所以结构如下:我有一个抽象类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 日历

告诉我它在Date 类中找不到函数operator=(显然是因为它是纯虚拟的)。为什么不使用任何被覆盖的? 它告诉我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 的 sn-p:

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;

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

【问题讨论】:

  • 您将什么作为模板参数发送给cal_lib::Calendar
  • 你有明确的operator=s 用于公历和儒略类型之间的转换吗?
  • Calendar 是如何实例化的?
  • 我不得不问你为什么不只使用Calendar() : today() { } 而不是你目前使用的默认然后分配方法?另外我想知道您的 MS 编译器版本是否正确处理协变返回类型。
  • @MarkB:如果编译器没有正确处理协变返回类型,我会感到非常惊讶。

标签: c++ oop templates linker overriding


【解决方案1】:

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

Date& operator=(const Date&)

而符号是从定义中需要的:

Gregorian& operator=(const Gregorian&)

*那么这个操作符是怎么出现在你的程序中的呢? *

复制构造函数和赋值运算符是特殊的,它们总是会在程序中声明。要么你提供一个声明,要么编译器会为你做。您提供了Gregorian&amp; Gregorian::operator=(const Date&amp;),但这不会从程序中删除Gregorian&amp; Gregorian::operator=(const Gregorian&amp;)

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

T& operator=( T const & o ) {
   Base::operator=(o);           // for all bases
   member=o.member;              // for all members
}

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

另一个需要更多努力的选项是为所有派生类型实际声明和定义赋值运算符。这里还有更多代码要写,处理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&amp;),此示例中的问题就会消失。

【讨论】:

  • 哦,我明白了,现在这个难以理解的错误信息终于变得有意义了!非常感谢您为帮助我所做的一切努力。 :)
  • @taperlawyer:错误消息通常非常明确,只需要几次就可以习惯并理解它们。
【解决方案2】:

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

编译器生成的函数的行为如下:

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

根据重载解析规则,这个函数更好万一

Gregorian g;
Gregorian u;
g = u;

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

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

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

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

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

【讨论】:

  • +1 用于检测整体问题,尽管我不太分享解决问题的两种方法。正确的方法是生成Date::operator= 来处理基础成员(编写的代码更少,更不容易出错,更易于维护)。特别是将Gregorian 日期分配给不同的Gregorian 日期在问题和您的实施中都被破坏了。
  • @DavidRodríguez-dribeas。你是有道理的。我希望我现在正确地写了所有东西。已经有点晚了,可能会错过。
  • 谢谢你帮助我,Lol4t0!非常感谢!
【解决方案3】:

说明问题的简化代码:

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 = - 这非常不方便......

【讨论】:

  • 我明白了,非常感谢您的意见!但我想知道,为什么将 A 的运算符定义为虚拟的没有意义?就我而言,我希望所有 Date 对象都具有赋值运算符,但根据子类的类型(Gregorian 或 Julian),它需要以不同的方式实现(或者我认为)。这不是你想要它虚拟的情况吗?
  • @taperlawyer 它在你的例子中没有意义——因为你不使用Date 作为可分配对象,就像在这个例子中void assign(Date&amp; a, const Date&amp; b) { a = b; }——只有在这里这个虚拟才有意义。在此示例中:Gregorian g; Julian j; g = j; - 它没有任何意义,因为您可以在没有任何虚拟性的情况下定义 Gregorian::operator = (Date&amp; a)
【解决方案4】:

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

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

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

注意返回值和const Date&amp;

【讨论】:

  • 它的定义完全一样(除了 r 在我的例子中被命名为 date )。在子类中,返回值分别为Gregorian&amp;Julian&amp;;我也尝试将它们更改为Date&amp;,以便签名完全匹配但没有结果。
  • @taperlawyer,请参阅 stackoverflow.com/questions/669818/… 以获得对虚拟作业的更好解释
猜你喜欢
  • 2021-11-08
  • 1970-01-01
  • 1970-01-01
  • 2012-12-14
  • 2020-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-29
相关资源
最近更新 更多