8

私は学校の課題のために小さなカレンダーライブラリを作成する最終段階にあり、予期しない非常に紛らわしい問題に遭遇しました。テンプレートを導入するときに、代入演算子が上書きされることはありません。

Dateしたがって、構造は次のようになります。ほとんど純粋仮想関数(代入演算子を含む)を持つ抽象クラスがあり、次に2つのサブクラスがGregorianあり、Julianそのすべての関数を実装します。最後に、クラスのテンプレートを作成しました。このテンプレートには、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;
    };
 }

そして、これが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_monthとの値m_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

エラー メッセージを注意深く読むと、次の 2 つのことがわかります。コンパイラは次の定義を見つけようとしています。

Date& operator=(const Date&)

そして、シンボルは次の定義から必要です。

Gregorian& operator=(const Gregorian&)

*では、その演算子はどのようにあなたのプログラムに現れたのでしょうか? *

コピー コンストラクターと代入演算子は特別であり、常にプログラムで宣言されます。宣言を提供するか、コンパイラーが宣言を行います。提供しましたが、それはプログラムからGregorian& Gregorian::operator=(const Date&)削除されません。Gregorian& Gregorian::operator=(const Gregorian&)

あるオブジェクトを別のオブジェクトに代入しようとするとGregorian、コンパイラは、ユーザーのオーバーロードと暗黙的に定義された 2 つのオーバーロードを検出し、オーバーロードの解決により、暗黙的に宣言された代入の方が一致することがわかります。これにより、次のような方法で代入演算子の定義がトリガーされます。

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

この問題を解決するためにできることはいくつかあります。最も単純なのは、おそらくDate Date::operator=(const Date&)プログラムで定義することです (純粋な仮想関数のままにしておきますが、定義も提供します)。このようにして、コンパイラが同じ派生型の 2 つのオブジェクトに遭遇すると、魔法のように機能し、すべてが機能します。派生型でディスパッチを強制することにより、ベースのメンバーのコピーの実装を除外する手段としてこれを使用することもできます。

より多くの労力を必要とする別のオプションは、すべての派生型の代入演算子を実際に宣言して定義することです。ここにはさらにコードを記述する必要があり、のメンバーを処理するコードを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、コンパイラはあなたのクラス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標準ドラフト

おそらく、Dateoperator=関数の実現を書く必要があります。おそらく、すべてのカレンダーの実現に同じ下線付きのデータ形式が必要です。そうすれば、仮想演算子=は必要ありません。ここで変換を実行しようとしていますが、正しく実行するには、 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; }

または-operator =クラスAから派生したすべてからデフォルトを削除します-これは非常に不便です...

于 2012-11-01T21:49:49.557 に答える
0

(明らかにそれは純粋に仮想であるため)。

本気ですか?それは正確にどのように定義されていますか? あなたはそれを間違って定義していると思います。次のように見えるはずです

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

戻り値と に注意してくださいconst Date&

于 2012-11-01T19:58:35.910 に答える