10

既存のライブラリに新しい機能を追加しようとしています。ルート クラスがアクセサを持つように、クラス階層に新しいデータを追加する必要があります。誰でもこのデータを取得できるのは、サブクラスのみが設定できるようにする必要があります (つまり、public getter と protected setter)。

下位互換性を維持するために、次のことを行ってはならないことはわかっています (リストには、問題に関連するアクションのみが含まれています)。

  • 仮想機能の追加または削除
  • メンバー変数の追加または削除
  • 既存のメンバー変数の型を変更する
  • 既存の関数のシグネチャを変更

このデータを階層に追加するには 2 つの方法が考えられます。新しいメンバー変数をルート クラスに追加する方法と、純粋な仮想アクセサー関数を追加する方法 (データをサブクラスに格納できるようにする方法) です。ただし、下位互換性を維持するために、これらのいずれも実行できません。

ライブラリは広範囲にpimplイディオムを使用していますが、残念ながら、私が修正しなければならないルート クラスはこのイディオムを使用していません。ただし、サブクラスはこのイディオムを使用します。

私が考えることができる唯一の解決策は、静的ハッシュマップでメンバー変数をシミュレートすることです。したがって、静的ハッシュ マップを作成し、この新しいメンバーをそこに格納して、静的アクセサーを実装することができます。このようなもの(疑似C ++で):

class NewData {...};

class BaseClass
{
protected:
    static setNewData(BaseClass* instance, NewData* data)
    {
        m_mapNewData[instance] = data;
    }

    static NewData* getNewData(BaseClass* instance)
    {
        return m_mapNewData[instance];
    }
private:
    static HashMap<BaseClass*, NewData*> m_mapNewData;      
};

class DerivedClass : public BaseClass
{
    void doSomething()
    {
        BaseClass::setNewData(this, new NewData());
    }
};

class Outside
{
   void doActions(BaseClass* action)
   {
       NewData* data = BaseClass::getNewData(action);
       ...
   }
};

さて、この解決策は機能するかもしれませんが、非常に醜いと思います (もちろん、非静的アクセサー関数を追加することもできますが、これでは醜さが取り除かれません)。

他の解決策はありますか?

ありがとうございました。

4

6 に答える 6

3

デコレータ パターンを使用できます。デコレータは新しいデータ要素を公開でき、既存のクラスを変更する必要はありません。これは、クライアントがファクトリを通じてオブジェクトを取得する場合に最適に機能します。これは、デコレータを透過的に追加できるためです。

于 2010-12-16T10:10:41.713 に答える
3

最後に、 abi-compliance-checkerなどの自動ツールを使用してバイナリの互換性を確認します。

于 2011-01-07T09:32:10.587 に答える
2

バイナリ互換性に影響を与えずにエクスポートされた関数 (declspec インポート/エクスポート) を追加できます (現在の関数を削除せず、最後に新しい関数を追加することを保証します)。ただし、新しいデータ メンバーを追加してクラスのサイズを増やすことはできません。

クラスのサイズを増やすことができない理由は、古いサイズを使用してコンパイルしたが、新しく拡張されたクラスを使用する人にとっては、オブジェクトのクラスの後にデータ メンバーが格納されることを意味するためです (複数の単語を追加すると、さらに多くの単語が追加されます)。新しいクラスの終わりまでに破棄されます。

例えば

年:

class CounterEngine {
public:
    __declspec(dllexport) int getTotal();
private:
    int iTotal; //4 bytes
};

新しい:

class CounterEngine {
    public:
        __declspec(dllexport) int getTotal();
        __declspec(dllexport) int getMean();
    private:
        int iTotal; //4 bytes
        int iMean;  //4 bytes
    };

クライアントには次のものが含まれる場合があります。

class ClientOfCounter {
public:
    ...
private:
    CounterEngine iCounter;
    int iBlah;  
};  

メモリ内では、古いフレームワークの ClientOfCounter は次のようになります。

ClientOfCounter: iCounter[offset 0],
                 iBlah[offset 4 bytes]

その同じコード (再コンパイルされていませんが、新しいバージョンを使用すると次のようになります)

ClientOfCounter: iCounter[offset 0],
                 iBlah[offset 4 bytes]  

つまり、iCounter が 4 バイトではなく 8 バイトになっていることを認識していないため、iBlah は実際には iCounter の最後の 4 バイトによって破棄されます。

予備のプライベート データ メンバーがある場合は、Body クラスを追加して、将来のデータ メンバーを格納できます。

class CounterEngine {
public:
    __declspec(dllexport) int getTotal();
private:
    int iTotal; //4 bytes
    void* iSpare; //future
};

  class CounterEngineBody {
    private:
        int iMean; //4 bytes
        void* iSpare[4]; //save space for future
    };


   class CounterEngine {
    public:
        __declspec(dllexport) int getTotal();
        __declspec(dllexport) int getMean() { return iBody->iMean; }
    private:
        int iTotal; //4 bytes
        CounterEngineBody* iBody; //now used to extend class with 'body' object
    };
于 2011-01-07T10:20:33.537 に答える
1

ライブラリがオープンソースの場合、 upstream-trackerへの追加をリクエストできます。下位互換性のためにすべてのライブラリ リリースを自動的にチェックします。そのため、API を簡単に維持できます。

編集: qt4 ライブラリのレポートはこちらです。

于 2011-01-10T12:12:14.230 に答える
0

バイナリの互換性を維持するのは困難です。インターフェイスの互換性のみを維持する方がはるかに簡単です。

唯一の合理的な解決策は、現在のライブラリのサポートを中断し、クラスの純粋な仮想インターフェイスのみをエクスポートするように再設計することだと思います。

  • そのインターフェイスは今後変更できませんが、新しいインターフェイスを追加することはできます。
  • そのインターフェイスでは、ポインターや指定されたサイズの整数または浮動小数点数などのプリミティブ型のみを使用できました。たとえば std::strings やその他の非プリミティブ型とのインターフェースを持つべきではありません。
  • DLL に割り当てられたデータへのポインターを返す場合、アプリケーションが DLL の削除を使用してデータの割り当てを解除できるように、割り当て解除用の仮想メソッドを提供する必要があります。
于 2010-12-16T10:46:30.123 に答える
-1

ルートにデータ メンバーを追加すると、バイナリ互換性が失われます (懸念がある場合は再構築が強制されます) が、下位互換性は失われず、メンバー関数 (仮想であろうとなかろうと) も追加されません。新しいメンバー関数を追加することは、当然の方法です。

于 2010-12-16T10:20:06.487 に答える