2016-09-23 6 views
2

私は抽象的なインターフェイスに対してコード化し、使用するコンパイル時にコンパイル時に選択できるように、同様の機能を持つ2つのサードパーティライブラリの共通インターフェイスを作成しようとしています。効率的で洗練されたインターフェイスの抽象化

オーバーヘッドを追加しないようにするには、この抽象的なインターフェイスが必要です。つまり、多態性は問題にはなりません。いずれにしても実際に実装されている実装が1つしかないので、これは必要ありません。だから私の最初の試みは、このように見えた:

AbstractInterface.h:

// Forward declarations of abstract types. 
class TypeA; 
class TypeB; 
class TypeC; 

TypeA *foo(TypeA *a, TypeB *b); 
TypeB *bar(std::vector<TypeC*> &c); 
TypeC *baz(TypeC *c, TypeA *c); 

ImplementationOne.cpp:

class ActualTypeA {...}; 
using TypeA = ActualTypeA; // Error! 
... 

残念ながら、これはタイプAであることを言って、コンパイルエラーになりフォワード宣言はそれがクラスであるというよりも何も言わなかったとしても、異なる型を使って再定義されています。だから私が試した次の事は、このでした:ここ

class TypeA : public ActualTypeA {}; // No more error 
... 
TypeA *foo(TypeA *a, TypeB *b) 
{ 
    return actualFoo(a, b); // Error 
} 

、actualFoo()が自動的に*タイプAに変換することはできませんActualTypeA *を返します。 *私が誤ってタイプAにActualTypeA以外の何か*をキャストしないように

inline TypeA *A(ActualTypeA *a) 
{ 
    return reinterpret_cast<TypeA*>(a); 
}  

TypeA *foo(TypeA *a, TypeB *b) 
{ 
    return A(actualFoo(a, b)); 
} 

理由私は(ヘルパー関数Aを使用しています)です。だから私のような何かにそれを書き換える必要があります。とにかく、私の実際のインターフェースは実装ごとに数万行のコードなので、私はこの解決策に興奮していません。そして、すべてのA()、B()、C()などは、読みにくくする。任意の実装側の変更を必要と回避このすべてについて移動する

inline std::vector<ActualTypeC*> &C(std::vector<TypeC*> &t) 
{ 
    return reinterpret_cast<std::vector<ActualTypeC*>&>(t); 
} 

TypeB *bar(std::vector<TypeC*> &c) 
{ 
    B(actualBar(C(c)); 
} 

もう一つの方法は、::

AbstractInterface.h

さらに、バーの実装では、()いくつかの追加のブードゥー教が必要になります:

class ActualTypeA; 
class ActualTypeB; 
class ActualTypeC; 

namespace ImplemetationOne 
{ 
    using TypeA = ActualTypeA; 
    using TypeB = ActualTypeB; 
    using TypeC = ActualTypeC; 
} 

class OtherActualTypeA; 
class OtherActualTypeB; 
class OtherActualTypeC; 

namespace ImplemetationTwo 
{ 
    using TypeA = OtherActualTypeA; 
    using TypeB = OtherActualTypeB; 
    using TypeC = OtherActualTypeC; 
} 

// Pre-define IMPLEMENTATION as ImplementationOne or ImplementationTwo 
using TypeA = IMPLEMENTATION::TypeA; 
using TypeB = IMPLEMENTATION::TypeB; 
using TypeC = IMPLEMENTATION::TypeC; 

TypeA *foo(TypeA *a, TypeB *b); 
TypeB *bar(std::vector<TypeC*> &c); 
TypeC *baz(TypeC *c, TypeA *c); 

これは、誰かが誤って実装固有を使用する可能性のある問題を持っています抽象的なものの代わりに型を使用します。また、このヘッダーを含むコンパイル単位ごとにIMPLEMENTATIONを定義する必要があり、一貫性が必要です。私はむしろ単にImplementationOne.cppかImplementationTwo.cppのどちらかをコンパイルしたいと思います。もう1つの欠点は、インプリメンテーション固有のタイプに実際に関心がない場合でも、ヘッダを変更する必要があることです。

これは非常に一般的な問題のように思われるので、よりエレガントで効率的なソリューションが見つからないのでしょうか?

+0

あなたは継承が解決しようとしていた問題を解決するための広大な努力、英雄的な努力に向かいそうです。コンパイル時のインターフェースを探しているので、テンプレートを見る必要があります。 –

+1

余分な機能を呼び出すことはオーバーヘッドであるため、問題の解決策にはなりません。一言で言えば、私はあなたが「コストはかかりません」と他の人が「確かにそれはコストがかかりません」というプロファイリングや確実性のないランダムなものを選んでいると考えています。 2つのインタフェースは、実際にメソッドと関数のためのメソッドであり、タイプ全体の型の関数と型ですか? – Yakk

+0

実装を選択する情報が利用可能な場合は、タグディスパッチまたはSFINAEを使用してコンパイル時にインプリメンテーションを選択できます。コンパイル時の多型ですが、実行時の多形性を避けたいと思うのです。 – midor

答えて

0

C++は、前方宣言されたクラスを既存のクラスとして定義する方法をサポートしていません。だから私はキャスト(とヘルパー関数)のアプローチをとにかく使ってしまった。これは650行に変更されましたが、少なくともオーバーヘッドを追加しないことが保証されています。私は将来的にそれを容易にするため、このための言語機能を追加(または単に無生成するために再定義エラーを使用して/のtypedefリラックス)してC++標準化委員会に提案すると思う

...

-1

まったく同じ機能を実装するライブラリの周りに2つのラッパーを記述します。次に、その実装の1つだけをコンパイルしてリンクします。小さなオーバーヘッドがあります。実際のゼロオーバーヘッドを得るには、ラッパーヘッダーのみを作成し、実装を変更するときにプロジェクト全体を再構築する必要があります。

マクロを使用して実装を選択することも、ビルドシステムでソースのサブセットのみをコンパイルすることもできます。

+0

あなたのソリューションは実際の問題を繰り返すようです。同じインターフェースを実装する2つのラッパーを作成するにはどうすればよいですか?最も明白な答えは、キャストを使うことだと思われますが、それほどエレガントではありません。型を宣言してそれを既存の型と同じにする方法はありますか? –

+0

私の解決策は2つの実装を記述することですが、それらのうちの1つだけをコンパイルすることです。ビルドシステムを使用して、現在必要でないファイルをスキップするか、#ifdef endの下に完全に実装するかのどちらか一方だけを残します。 –

+0

つまり、異なるバックエンドライブラリを持つ2種類のアプリケーションをビルドしたい場合は、ビルドシステムのための作業であり、言語習得のための作業ではありません。 –

-1

「多型が意味するものはありません」

なぜですか?私は、相続はあなたが望むものだと言うでしょう...多型には何が問題なのですか?遅すぎる?何のために?あなたが望むものはそれほど遅いですか?あるいは、あなた自身に任意の制約を与えているだけですか?あなたの問題の説明から私が理解したことを考えると、ポリモルフィズムはあなたが欲しいものです!契約を定義する基本クラスB(メソッドのセット)を定義したいとします。あなたのプログラム全体は基本クラスだけを知っていて、Bから派生したクラスを決して参照することはありません。次に、B-CとDから派生した2つ以上のクラスを実装します。プログラムは、Bを知っているだけで、CやDのコードが実際に起こるかどうかを気にせずにメソッドを呼び出すだけです!とにかく多形性に対して何を持っていますか?これはOOPの基礎の一つなので、C++の使用をやめてC言語に固執することができます。

+0

これは、すでに複数のクライアントによって使用されている高性能フレームワークです。 2番目の実装を可能にするためにインタフェースを抽象化している間は、現在の実装ではパフォーマンスが後退することを許していません。また、これらの実装では、自分のコントロールできないサードパーティのライブラリを使用するため、共通の基底クラスからクラスを派生することはできません。私はそれらを包み込むことができましたが、私はそれらをアップコンバートするために必要なキャストに戻っています。 –

0

特色を使うことができます。
最小限の作業例を次に示します。

#include<type_traits> 
#include<vector> 

struct ActualTypeA {}; 
struct ActualTypeB {}; 
struct ActualTypeC {}; 

struct OtherActualTypeA {}; 
struct OtherActualTypeB {}; 
struct OtherActualTypeC {}; 

enum class Lib { LibA, LibB }; 

template<Lib> 
struct Traits; 

template<> 
struct Traits<Lib::LibA> { 
    using TypeA = ActualTypeA; 
    using TypeB = ActualTypeB; 
    using TypeC = ActualTypeC; 
}; 

template<> 
struct Traits<Lib::LibB> { 
    using TypeA = OtherActualTypeA; 
    using TypeB = OtherActualTypeB; 
    using TypeC = OtherActualTypeC; 
}; 

template<Lib L> 
struct Wrapper { 
    using LibTraits = Traits<L>; 

    static typename LibTraits::TypeA *foo(typename LibTraits::TypeA *a, typename LibTraits::TypeB *b) { return nullptr; } 
    static typename LibTraits::TypeB *bar(std::vector<typename LibTraits::TypeC*> &c) { return nullptr; } 
    static typename LibTraits::TypeC *baz(typename LibTraits::TypeC *c, typename LibTraits::TypeA *a) { return nullptr; } 
}; 

int main() { 
    using MyWrapper = Wrapper<Lib::LibB>; 
    static_assert(std::is_same<decltype(MyWrapper::foo(nullptr, nullptr)), OtherActualTypeA*>::value, "!"); 
    static_assert(std::is_same<decltype(MyWrapper::baz(nullptr, nullptr)), OtherActualTypeC*>::value, "!"); 
} 
+0

これは、IMPLEMENTATIONマクロを使用するソリューションと同じ問題があるようです。 –

-1

することができます。もちろん、

「私はパフォーマンスは現在の実装のための退行を許可することはできません」。ただし、パフォーマンスが回帰することを許可することはできませんtooあなたは、多形性で失うパフォーマンスの正確さを知っていますか?あなたはパフォーマンスを失いますが、正確にどれくらいの割合を失いますか?適切な計測器でテストバージョンを実装しようとしたことがありますか?それは、それが減速の原因となる多形呼び出しであることを確かめてください。それらの多型呼び出しがどれくらい頻繁に行われるか知っていますか?時には、解決しようとする問題に対する洗練された解決策であるポリモーフィックな呼び出しを削除することでパフォーマンスを向上させることはできますが、結果をキャッシュしたり、バンドルしたりするなど、インターフェースを煩雑にしないでください。 Sが700ミリ秒より遅いため、明らかな解Sを排除するために、Sが1時間に6回使用されることを見つけるには...:S

それ以外の場合は同じcppファイルの2つの異なる実装を持つことができます。あなたのビルドプロセスを2回、つまり各cppバージョンごとに1回実行してください。

+0

"間接指示の層が多すぎるという問題を除いて、コンピュータサイエンスのすべての問題は別のレベルの間接指示によって解決できます。 –

関連する問題