C ++の内部typedefs-良いスタイルか悪いスタイルか?


179

私が最近頻繁に行っているのは、そのクラス内の特定のクラスに関連するtypedefを宣言することです。

class Lorem
{
    typedef boost::shared_ptr<Lorem> ptr;
    typedef std::vector<Lorem::ptr>  vector;

//
// ...
//
};

これらのタイプは、コードの他の場所で使用されます。

Lorem::vector lorems;
Lorem::ptr    lorem( new Lorem() );

lorems.push_back( lorem );

私が好きな理由:

  • クラステンプレートによって導入されたノイズを減らし、にstd::vector<Lorem>なりますLorem::vector
  • これは意図の表明として機能します。上記の例では、Loremクラスは参照カウントboost::shared_ptrされ、ベクトルに格納されることを目的としています。
  • これにより、実装の変更が可能になります。つまりboost::intrusive_ptr、後の段階でLoremを(を介して)侵入参照参照に変更する必要がある場合、コードへの影響は最小限になります。
  • 私はそれは「きれい」に見え、間違いなく読みやすいと思います。

気に入らない理由:

  • 依存関係に問題がある場合があります-たとえば、Lorem::vector別のクラス内にを埋め込みたいが、(ヘッダーファイルに依存関係を導入するのではなく)Loremを転送宣言するだけでよい(またはしたい)場合は、最終的に明示的な型(たとえば、boost::shared_ptr<Lorem>ではなくLorem::ptr)。これは少し矛盾します。
  • それはあまり一般的ではないかもしれません、それゆえ理解するのは難しいですか?

私は自分のコーディングスタイルで客観的になるように努めていますので、私の考えを少し分析できるように、それについて他の意見を得ることは良いでしょう。

回答:


153

素晴らしいスタイルだと思いますし、自分で使っています。名前のスコープをできる限り制限することが常に最善であり、C ++でこれを行うにはクラスの使用が最善の方法です。たとえば、C ++標準ライブラリは、クラス内でtypedefを頻繁に使用します。


それは良い点です。「きれいに見える」とは、潜在意識が限定的なスコープが良いことだと微妙に指摘していたこと思います。けれども、STLがクラステンプレートで主にSTLを使用しているという事実は、微妙に異なる使用法になるのでしょうか?「コンクリート」クラスで正当化するのは難しいですか?
ウィルベイカー、

1
標準ライブラリはクラスではなくテンプレートで構成されていますが、正当化はどちらも同じだと思います。

14

これは意図のステートメントとして機能します。上記の例では、Loremクラスはboost :: shared_ptrを介して参照カウントされ、ベクトルに格納されることを目的としています。

これはまさにそれが行わないことです。

コードに「Foo :: Ptr」が表示されている場合、それがshared_ptrかFoo *(STLにはT:の:: typetypesが含まれているのか、覚えている)かどうかはまったくわかりません。特に 共有ポインターの場合、typedefはまったく提供しませんが、コードでshared_ptrを明示的に使用し続けます。

実際、テンプレートメタプログラミング以外でtypedefを使用することはほとんどありません。

STLは常にこの種のことを行います

メンバー関数とネストされたtypedefの概念で定義された概念を持つSTL設計は、歴史的な袋小路であり、最新のテンプレートライブラリは組み込みの型を除外しないため、無料の関数と特性クラス(Boost.Graphを参照)を使用します。コンセプトをモデル化すること、および特定のテンプレートライブラリのコンセプトを念頭に置いて設計されていないタイプを容易に適合させることができるため。

同じ間違いをする理由としてSTLを使用しないでください。


私はあなたの最初の部分に同意しますが、あなたの最近の編集は少し近視眼的です。このようなネストされた型は、実用的なデフォルトを提供するため、特性クラスの定義を単純化します。新しいstd::allocator_traits<Alloc>クラスについて考えてみましょう...から直接型を借りるだけなので、作成するアロケータごとにクラスを特殊化する必要はありませんAlloc
Dennis Zickefoose

@Dennis:C ++では、利便性はライブラリの/ user /側にあり、/ author /側にあるべきではありません。ユーザーは特性の統一インターフェースを望んでおり、特性クラスのみがそれを提供できます。上記の理由により)。しかし、Alloc作者としてもstd::allocator_traits<>、必要なtypedefを追加することよりも、新しい型に特化することは難しいことではありません。返信全体をコメントに含めることができなかったため、回答も編集しました。
Marc Mutz-mmutz

しかし、それユーザーの側にあります。ユーザーallocator_traitsカスタムアロケータを作成しようと、私は私がしなければならないすべては言っている...特性クラスの15人のメンバーを気にする必要はありませんtypedef Blah value_type;し、適切なメンバ関数を提供し、デフォルトではallocator_traits把握します残り。さらに、Boost.Graphの例を見てください。はい、それは特性クラスを多用します...しかし、それ自身の内部のtypedefのためのgraph_traits<G>単にクエリのデフォルト実装 G
Dennis Zickefoose

1
そして03標準ライブラリでさえ、必要に応じて特性クラスを使用します...ライブラリの哲学は、コンテナを一般的に操作するのではなく、イテレータを操作することです。そのiterator_traitsため、汎用アルゴリズムが適切な情報を簡単に照会できるようにクラスを提供します。これも、デフォルトでイテレータに独自の情報を問い合わせることになります。長所と短所は、特性と内部のtypedefはほとんど相互に排他的ではないということです...それらは互いにサポートします。
Dennis Zickefoose

1
@Dennis:のモデルでiterator_traitsある必要があるためT*必要になりましたRandomAccessIteratorが、必要なtypedefをに入れることができませんT*。一度iterator_traits、入れ子になったtypedefが冗長になり、それがそこから削除されていればよかったのですが。同じ理由(内部のtypedefを追加できないこと)T[N]は、STLのSequence概念をモデル化しておらず、などのkludgeが必要std::array<T,N>です。Boost.RangeはT[N]、ネストされたtypedefやメンバー関数を必要としないため、モデル化できる最新のシーケンスコンセプトをどのように定義できるかを示しています。
マルク・ムッツ-mmutz


8

typdefは間違いなく優れたスタイルです。そして、あなたの「私が好きな理由」はすべて良くて正しいです。

それに関してあなたが抱えている問題について。まあ、前方宣言は聖杯ではありません。マルチレベルの依存関係を回避するようにコードを設計するだけです。

typedefをクラスの外に移動することはできますが、Class :: ptrはClassPtrよりもはるかにきれいなので、私はこれを行いません。それは私にとって名前空間のようなものです-物事はスコープ内で接続されたままです。

たまにやった

Trait<Loren>::ptr
Trait<Loren>::collection
Trait<Loren>::map

そして、それはすべてのドメインクラスに対してデフォルトであり、特定のもののためにいくらか特殊化することができます。


3

STLは常にこのタイプのことを行います。typedefはSTLの多くのクラスのインターフェースの一部です。

reference
iterator
size_type
value_type
etc...

さまざまなSTLテンプレートクラスのインターフェイスの一部であるすべてのtypedefです。


確かに、これは私が最初に拾った場所だと思います。これらは正当化するのが少し簡単になるようですが?たまたま「メタプログラミング」の行に沿って考えると、クラステンプレート内のtypedefを変数に似ていると見るしかありません。
ウィルベイカー、

3

これに対する別の投票は良い考えです。時間と空間の両方で効率的でなければならないシミュレーションを書くときに、私はこれを始めました。すべての値の型には、ブースト共有ポインターとして開始されたPtr typedefがありました。次に、いくつかのプロファイリングを行い、これらのオブジェクトが使用されているコードを変更せずに、ブースト侵入型ポインターに変更しました。

これが機能するのは、クラスが使用される場所がわかっている場合のみであり、すべての使用に同じ要件があることに注意してください。たとえば、ライブラリを記述するときに、それが使用されるコンテキストを知ることができないため、これをライブラリコードで使用しません。


3

現在私はコードに取り組んでおり、この種のtypedefを集中的に使用しています。これまでのところそれで結構です。

しかし、反復typedefがかなり頻繁にあり、定義がいくつかのクラスに分割されており、どの型を処理しているのか本当に分からないことに気づきました。私の仕事は、これらのtypedefの背後に隠されているいくつかの複雑なデータ構造のサイズを要約することです。そのため、既存のインターフェースに依存することはできません。3〜6レベルのネストされた名前空間と組み合わせると、混乱を招きます。

それらを使用する前に、考慮すべきいくつかのポイントがあります

  • 他の誰かがこれらのtypedefを必要としますか?クラスは他のクラスでよく使用されていますか?
  • 使用方法を短くするか、クラスを非表示にしますか?(非表示の場合は、インターフェースについても考えることができます。)
  • 他の人がコードを扱っていますか?どうやってやっているの?彼らはそれが簡単だと思いますか、それとも混乱しますか?

1

これらのtypedefをクラスの外に移動することをお勧めします。このようにして、共有ポインターとベクトルクラスへの直接の依存関係を削除し、必要なときにのみそれらを含めることができます。クラスの実装でこれらの型を使用しているのでない限り、それらは内部のtypedefであってはならないと私は考えています。

これらはクラス内で宣言するのではなく、typedefを介した型エイリアスによって解決されるため、この理由は依然として一致しています。


それは匿名の名前空間をtypedefで汚染しますね?!typedefの問題は、実際の型を非表示にすることです。これにより、複数のモジュールに含まれる場合や複数のモジュールに含まれる場合に競合が発生する可能性があり、検索や修正が困難になります。これらを名前空間またはクラス内に含めることをお勧めします。
Indy9000 2009

3
名前の競合と匿名の名前空間の汚染は、クラスの内部または外部で型名を保持することとはほとんど関係がありません。typedefとではなく、クラスと名前が競合する可能性があります。したがって、名前の汚染を避けるために、名前空間を使用します。名前空間でクラスと関連するtypedefを宣言します。
カタリンPitiş

typedefをクラス内に配置するもう1つの引数は、テンプレート化された関数の使用です。たとえば、関数が不明な文字列型(文字列または独自の文字列準拠のバリアント)を含む不明なコンテナー型(ベクトルまたはリスト)を受け取った場合。コンテナーペイロードのタイプを把握する唯一の方法は、コンテナークラス定義の一部であるtypedef 'value_type'を使用することです。
マリウス

1

typedefがクラス自体の中でのみ使用されている場合(つまり、プライベートとして宣言されている場合)、それは良い考えだと思います。しかし、あなたが与える理由のために、typedefがクラスの外で知られる必要があるなら、私はそれを使いません。その場合は、クラスの外に移動することをお勧めします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.