5

更新:「それは望まない、代わりにこれが欲しい」という提案に感謝します。それらは、特にやる気を起こさせるシナリオのコンテキストで提供される場合に役立ちます。それでも...善/悪に関係なく、私は「C++11で合法的に行うことができるはい」と「いいえそのようなことを行うことは不可能」を見つけるのが難しいことに興味を持っています。


いくつかのヘルパーメソッドを追加することのみを目的として、オブジェクトポインタを別のタイプとして「エイリアス」したいと思います。エイリアスは、基になるクラスにデータメンバーを追加できません(実際、それが発生するのを防ぐことができれば、それだけうまくいきます!)すべてのエイリアスは、このタイプのすべてのオブジェクトに等しく適用できます...型システムが次のことを示唆できる場合に役立ちます。エイリアスがおそらく最も適切です。

基になるオブジェクトにエンコードされた特定のエイリアスに関する情報はありません。したがって、型システムを「ごまかす」ことができ、それを注釈にすることができるはずです...コンパイル時にチェックされますが、最終的にはランタイムキャストとは無関係です。これらの線に沿った何か:

Node<AccessorFoo>* fooPtr = Node<AccessorFoo>::createViaFactory();
Node<AccessorBar>* barPtr = reinterpret_cast< Node<AccessorBar>* >(fooPtr);

内部的には、ファクトリメソッドは実際にNodeBaseクラスを作成し、同様のものを使用してreinterpret_castそれをとして返しNode<AccessorFoo>*ます。

これを回避する簡単な方法は、ノードをラップし、値によって渡されるこれらの軽量クラスを作成することです。したがって、キャストは必要ありません。ノードハンドルを取得してコンストラクターでラップするAccessorクラスだけです。

AccessorFoo foo (NodeBase::createViaFactory());
AccessorBar bar (foo.getNode());

しかし、私がそのすべてにお金を払う必要がなければ、私はしたくありません。これには、たとえば、ラップされたポインターの種類ごとに特別なアクセサー型を作成することが含まれます(AccessorFooShared、AccessorFooUnique、AccessorFooWeakなど)。これらの型付きポインターは、単一のポインターベースのオブジェクトIDに対してエイリアス化されることが望ましく、素敵な直交性。

では、元の質問に戻りましょう。

Node<AccessorFoo>* fooPtr = Node<AccessorFoo>::createViaFactory();
Node<AccessorBar>* barPtr = reinterpret_cast< Node<AccessorBar>* >(fooPtr);

これを行うには、醜いかもしれないが「ルールを破る」ことはできない方法があるようです。ISO14882:2011(e)5.2.10-7によると:

オブジェクトポインタは、別のタイプのオブジェクトポインタに明示的に変換できます。70タイプ「pointertoT1」のprvaluevがタイプ「pointertocv T2」に変換されると、結果はstatic_cast(static_cast(v))になります。 T1とT2の両方が標準レイアウトタイプ(3.9)であり、T2のアライメント要件がT1のアライメント要件よりも厳密でない場合、またはいずれかのタイプが無効である場合。タイプ「pointertoT1」のprvalueをタイプ「pointertoT2」(T1とT2はオブジェクトタイプであり、T2のアライメント要件はT1の要件よりも厳密ではない)に変換して元のタイプに戻すと、元のタイプが生成されます。ポインタ値。他のそのようなポインタ変換の結果は指定されていません。

「標準レイアウトクラス」の定義を掘り下げると、次のことがわかります。

  • タイプnon-standard-layout-class(またはそのようなタイプの配列)または参照の非静的データメンバーがなく、
  • 仮想関数(10.3)と仮想基本クラス(10.1)がなく、
  • すべての非静的データメンバーに対して同じアクセス制御(11節)があり、
  • 非標準レイアウトの基本クラスはなく、
  • 最も派生したクラスに非静的データメンバーがなく、非静的データメンバーを持つ基本クラスが多くても1つないか、非静的データメンバーを持つ基本クラスがない。
  • 最初の非静的データメンバーと同じタイプの基本クラスはありません。

このようなもので作業することは、アクセサーまたはノードに仮想メソッドがない状態で私の手を少し縛るようなもののように聞こえます。それでも、C++11は明らかにstd::is_standard_layout物事をチェックし続けなければなりません。

これは安全に行うことができますか?gcc-4.7で動作するように見えますが、未定義の動作を呼び出さないようにしたいと思います。

4

4 に答える 4

5

厳密なエイリアシングルールは、あなたがやろうとしていることを禁じていると思います。

明確にするために:厳密なエイリアシングは、レイアウトの互換性、PODタイプなどとは何の関係もありません。それは最適化と関係があります。そして、言語が明示的にあなたがすることを禁じていることで。

この論文はそれをかなりうまくまとめています:http://dbp-consulting.com/StrictAliasing.pdf

于 2012-07-02T23:15:32.717 に答える
3

私があなたを正しく理解しているなら、あなたは以下を持っています:

  • NodeBaseステートフルなクラスであり、システムの真の主力製品です。
  • ;へのインターフェースを提供するステートレスAccessorタイプのセット。NodeBase
  • Node<AccessorT>アクセサをラップするクラスで、おそらく便利な関数を提供します。

便利なことを行うラッパータイプがない場合は、Accessor提案したように、タイプをトップレベルにしない理由はないので、最後のビットを想定します。値で渡しAccessorFooますAccessorBar。それらが同じオブジェクトではないという事実は完全に議論の余地があります。それらをポインタのように考えると、もちろん、それ&foo != &barを持って注意することほど興味深いことではないことに気付くでしょう。NodeBase* p1 = new NodeBase; NodeBase* p2 = p1;&p1 != &p2

本当にラッパーが必要で、Node<AccessorT>それを標準レイアウトにしたい場合は、Accessorタイプのステートレス性を活用することをお勧めします。それらが単に機能のステートレスコンテナである場合(そうである必要があります。他になぜそれらを自由にキャストできるのでしょうか?)、次のようなことができます。

struct AccessorFoo {
    int getValue(NodeBase* n) { return n->getValueFoo(); }
};

struct AccessorBar {
    int getValue(NodeBase* n) { return n->getValueBar(); }
};

template <typename AccessorT>
class Node {
    NodeBase* m_node;

public:
    int getValue() {
        AccessorT accessor;
        return accessor.getValue(m_node);
    }
};

この場合、テンプレート化された変換演算子を追加できます。

template <typename OtherT>
operator Node<OtherT>() {
    return Node<OtherT>(m_node);
}

そして今、あなたはNode<AccessorT>あなたが好きなタイプの間で直接値変換を持っています。

もう少し進んでいくと、Accessorタイプのすべてのメソッドが静的になり、特性パターンに到達します。


ちなみに、引用したC ++標準のセクションはreinterpret_cast<T*>(p)、ソース型と最終型の両方が標準レイアウトオブジェクトへのポインタである場合の動作に関係しています。この場合、標準は同じポインタを取得することを保証します。 dキャストからavoid*に、次に最終タイプに移動します。未定義の動作を呼び出さずに、オブジェクトを作成されたタイプ以外のタイプとして使用することはできません。

于 2012-06-27T04:56:36.500 に答える
3

アクセサーという用語は完全な景品です。あなたが探しているのはプロキシです。

プロキシが値によって渡されない理由はありません。

// Let us imagine that NodeBase is now called Node, since there is no inheritance

class AccessorFoo {
public:
    AccessorFoo(Node& n): node(n) {}

    int bar() const { return node->bar; }

private:
    std::reference_wrapper<Node> node;
};

そして、あるアクセサから別のアクセサに自由に変換できます...これはにおいがしますが。通常、アクセサーを持つことのまさに目標は、ある方法でアクセスを制限することであるため、別のアクセサーにnillywillyをキャストすることは悪いことです。ただし、より狭いアクセサーへのキャストをサポートすることもできます。

于 2012-06-27T06:40:21.360 に答える
2
struct myPOD {
   int data1;
   // ...
};

struct myPOD_extended1 : myPOD {
   int helper() { (*(myPOD*)this)->data1 = 6; };  // type myPOD referenced here
};
struct myPOD_extended2 : myPOD { 
   int helper() { data1 = 7; };                   // no syntactic ref to myPOD
};
struct myPOD_extended3 : myPOD {
   int helper() { (*(myPOD*)this)->data1 = 8; };  // type myPOD referenced here
};
void doit(myPOD *it)
{
    ((myPOD_extended1*)it)->helper();
    // ((myPOD_extended2*)it)->helper(); // uncomment -> program/compile undefined
    ((myPOD_extended3*)it)->helper();
}

int main(int,char**)
{
    myPOD a; a.data1=5; 
    doit(&a);

    std::cout<< a.data1 << '\n';
    return 0;
}

これはすべての準拠C++コンパイラで機能することが保証されており、8を出力する必要があると思います。マークされた行のコメントを外すと、すべての賭けがオフになります。

オプティマイザは、関数で実際に参照されている構文タイプのリストを、正しい結果を生成するために必要な構文タイプの参照のリスト(3.10p10)と照合することにより、有効なエイリアス参照の検索を削除する場合があります。 ")オブジェクトタイプは既知です。そのリストには、派生タイプへの参照を介したアクセスは含まれていません。したがって、s内のthistoの明示的な(そして有効な)ダウンキャストは、構文的に参照されるタイプのリストに追加され、オプティマイザーは、結果の参照をオブジェクト(の別名、エイリアス)への潜在的な法的参照として処理する必要があります。myPOD*helper()myPODa

于 2012-07-07T19:54:02.883 に答える