25

暗黙的な変換は、型が意味的に同等である場合に非常に役立ちます。たとえば、同じ型を実装しているが名前空間が異なる 2 つのライブラリがあるとします。または、あちこちのセマンティックシュガーを除いて、ほとんど同じタイプです。関数がテンプレートでない限り、一方の型を他方を使用するように設計された (これらのライブラリの 1 つの) 関数に渡すことはできません。そうでない場合は、何らかの方法で一方の型を他方の型に変換する必要があります。これは些細なことであるはずです (そうでなければ、型は結局のところそれほど同一ではありません!) が、変換を明示的に呼び出すと、ほとんど意味のない関数呼び出しでコードが肥大化します。このような変換関数は実際にはいくつかの値をコピーする可能性がありますが、高レベルの「プログラマー」の観点からは本質的に何もしません。

暗黙の変換コンストラクターと演算子は明らかに役立つ可能性がありますが、それらはカップリングを導入するため、それらの型の 1 つが他の型について知る必要があります。通常、少なくともライブラリを扱う場合はそうではありません。これらのタイプの 1 つが存在すると、他のタイプが冗長になるためです。また、常にライブラリを変更できるとは限りません。

これで、ユーザー コードで暗黙的な変換を機能させる方法について 2 つのオプションが表示されます。

  1. 1 つ目は、関連するすべての型に対して変換演算子と変換コンストラクタ (および代入) を実装するプロキシ型を提供し、常にそれを使用することです。

  2. 2 つ目は、ライブラリに最小限の変更を加えるだけで済みますが、大きな柔軟性が得られます。外部からオプションで有効にできる、関連する型ごとに変換コンストラクターを追加します。

たとえば、型の場合A、コンストラクターを追加します。

template <class T> A(
  const T& src,
  typename boost::enable_if<conversion_enabled<T,A>>::type* ignore=0
)
{
  *this = convert(src);
}

とテンプレート

template <class X, class Y>
struct conversion_enabled : public boost::mpl::false_ {};

デフォルトで暗黙的な変換を無効にします。

次に、2 つの型の間の変換を有効にするために、テンプレートを特殊化します。

template <> struct conversion_enabled<OtherA, A> : public boost::mpl::true_ {};

convertADL で見つけられる機能を実装します。

個人的には、強い反対意見がない限り、2 番目のバリアントを使用することを好みます。

実際の質問に移りましょう: 暗黙的な変換のために型を関連付ける好ましい方法は何ですか? 私の提案は良いアイデアですか?どちらのアプローチにも欠点はありますか? そのような変換を許可することは危険ですか? ライブラリの実装者は、一般に、使用されている可能性が最も高いソフトウェアで型が複製される可能性がある場合に 2 番目の方法を提供する必要があります (ここでは、これらのパッケージのほとんどが 3D を実装する 3D レンダリング ミドルウェアを考えています)。ベクター)。

4

6 に答える 6

7

私が気にするのであれば、他のオプションよりもあなたの「プロキシ」アプローチを好むでしょう。

問題の真実は、これが開発のすべての分野で非常に大きな問題であることがわかったため、その特定のライブラリとのやり取り以外でライブラリ固有の構成を使用することを避ける傾向があるということです。1 つの例として、さまざまなライブラリでのイベント/シグナルの処理が考えられます。自分のプロジェクト コードに不可欠なものとして既にブーストを選択しているので、自分のプロジェクト コード内のすべての通信に、意図的に boost::signals2 を使用しています。次に、使用している UI ライブラリへのインターフェイスを記述します。

別の例は文字列です。そこにあるすべての気の利いたUIライブラリは、文字列を再発明します。私のモデルとデータ コードはすべて標準バージョンを使用し、そのような型で動作する UI ラッパーへのインターフェイスを提供します... UI コンポーネントと直接対話している時点でのみ UI 固有のバージョンに変換します。

これは、さまざまな独立しているが同様の構造によって提供される多くの機能を活用できないことを意味し、これらの変換を処理するために多くの余分なコードを書いていますが、より良いライブラリと/または、プラットフォームを切り替える必要がある場合は、これらのことをすべてを通じて雑草にすることを許可していないため、切り替えがはるかに簡単になります.

つまり、基本的には、プロキシのアプローチを好むでしょう。私はすでにそれを行っているからです。私は、使用している特定のライブラリから距離を置く抽象レイヤーで作業し、それらの抽象化を、そのライブラリとやり取りするために必要な詳細でサブクラス化します。私は常にそれを行っているので、2 つのサードパーティ ライブラリ間で情報を共有したい小さな領域について疑問に思っていることは、基本的に既に回答されています。

于 2011-01-15T21:47:42.597 に答える
1

どちらのアプローチにも欠点はありますか? そのような変換を許可することは危険ですか? ライブラリの実装者は一般に、次の場合に 2 番目の方法を提供する必要があります...

一般に、速度に敏感なライブラリユーザーにとって不利益になるという点で、暗黙的な変換にはマイナス面があります (たとえば、内部ループで使用すると、おそらくそれに気付かない場合があります)。また、いくつかの異なる暗黙的な変換が利用可能な場合、予期しない動作が発生する可能性があります。したがって、一般的にライブラリの実装者が暗黙の変換を許可するのは悪いアドバイスだと思います。

あなたの場合、基本的に数値のタプル(A)を別のタプル(B)に変換するのは非常に簡単なので、コンパイラは変換をインライン化し、完全に最適化することができます。したがって、速度は問題ではありません。物事を混乱させる他の暗黙的な変換も存在しない可能性があります。したがって、利便性が勝つ可能性があります。ただし、暗黙的な変換を提供するかどうかの決定は、ケースバイケースで行う必要があり、そのようなケースはまれです。

2番目のバリアントで提案するような一般的なメカニズムは、ほとんど役に立たず、かなり悪いことを簡単に行うことができます。これを例に取ります(不自然ですが):

struct A {
    A(float x) : x(x) {}
    int x;
};

struct B {
    B(int y): y(y) {}
    template<class T> B(const T &t) { *this = convert(t); }
    int y;
};

inline B convert(const A &a) {
    return B(a.x+1);
}

この場合、テンプレート コンストラクターを無効にすると、B(20.0) の値が変更されます。つまり、暗黙的な変換コンストラクターを追加するだけで、既存のコードの解釈が変わる可能性があります。明らかに、それは非常に危険です。したがって、暗黙的な変換は一般的に利用可能であるべきではなく、価値があり、よく理解されている場合にのみ、非常に特定の型に対して提供されるべきです。2 番目のバリアントを保証するほど一般的ではありません。

要約すると、これは、サポートされるすべてのタイプを完全に理解して、ライブラリの外で行う方がよいでしょう。プロキシ オブジェクトは完璧に思えます。

于 2011-01-31T05:55:51.863 に答える
1

互換性のない型との間で暗黙的に変換できるコンバーター クラス (プロキシ) を作成できます。次に、コンストラクターを使用して、いずれかのタイプからプロキシを生成し、それをメソッドに渡すことができます。返されたプロキシは、目的の型に直接キャストされます。

欠点は、すべての呼び出しでパラメーターをラップする必要があることです。正しく実行すると、コンパイラはプロキシをインスタンス化せずに完全な呼び出しをインライン化します。また、クラス間の結合はありません。Proxy クラスだけがそれらを知る必要があります。

C++ をプログラミングしてからしばらく経ちましたが、プロキシは次のようになります。

class Proxy { 
  private:
    IncompatibleType1 *type1;
    IncompatibleType2 *type2;
    //TODO static conversion methods
  public:
    Proxy(IncompatibleType1 *type1) {
      this.type1=type1;
    }
    Proxy(IncompatibleType2 *type2) {
      this.type2=type2;
    }
    operator IncompatibleType1 * () { 
      if(this.type1!=NULL)
        return this.type1;
      else
        return convert(this.type2);
    }
    operator IncompatibleType2 * () { 
      if(this.type2!=NULL)
        return this.type2;
      else
        return convert(this.type1);
    }
}

呼び出しは常に次のようになります。

expectsType1(Proxy(type2));
expectsType1(Proxy(type1));
expectsType2(Proxy(type1));
于 2011-01-28T13:32:49.177 に答える
0

変換演算子のオーバーロードを使用できますか? 次の例のように:

class Vector1 {
  int x,y,z;
public:
  Vector1(int x, int y, int z) : x(x), y(y), z(z) {}
};

class Vector2 {
  float x,y,z;
public:
  Vector2(float x, float y, float z) : x(x), y(y), z(z) {}

  operator Vector1()  {
    return Vector1(x, y, z);
  }
};

これらの呼び出しが成功するようになりました。

void doIt1(const Vector1 &v) {
}

void doIt2(const Vector2 &v) {
}

Vector1 v1(1,2,3);
Vector2 v2(3,4,5);
doIt1(v1);
doIt2(v2);

doIt1(v2); // Implicitely convert Vector2 into Vector1
于 2011-01-28T14:39:46.287 に答える
0

最初のオプションについて:

関連するすべての型に対して変換演算子と変換コンストラクタ (および割り当て) を実装するプロキシ型を提供し、常にそれを使用します。

パフォーマンスが重要でない場合 (またはパフォーマンスが重要であり、データが基本的に文字列である場合) は、プロキシとして文字列 (テキスト) を使用できます。演算子<<and and を実装すると、テキストの中間表現を使用して変換>>できます。boost::lexical_cast<>

const TargetType& foo = lexical_cast<TargetType>(bar);

明らかに、パフォーマンスに非常に関心がある場合は、これを行うべきではなく、他の警告もあります (両方のタイプに適切なテキスト表現が必要です)。

于 2011-01-15T22:39:21.027 に答える
-2

今日はゆっくりです。プロキシ パターンを再度使用する際の問題は何でしたか? 私のアドバイスは、コピー機能が不必要な作業を行っていることを心配することに多くの時間を費やさないことです。また、明示的は良いです。

于 2011-01-30T02:13:31.043 に答える