10

標準で含まれている C++ ライブラリのロードにより、オブジェクトをライブラリで使用するように適合させることができます。多くの場合、同じ名前空間内のメンバー関数またはフリー関数のどちらかを選択します。

ライブラリコードがこれらの「拡張」関数の1つを呼び出す呼び出しをディスパッチするために使用するメカニズムと構成を知りたいのですが、この決定はコンパイル時に行う必要があり、テンプレートが関係していることを知っています。次のランタイム疑似コードは不可能/無意味です。理由はこの質問の範囲外です。

if Class A has member function with signature FunctionSignature
    choose &A.functionSignature(...)
else if NamespaceOfClassA has free function freeFunctionSignature
    choose freeFunctionSignature(...)
else
    throw "no valid extension function was provided"

上記のコードは、ランタイム コードのように見えます:/. では、ライブラリはクラスが存在する名前空間をどのように把握するのか、3 つの条件をどのように検出するのか、回避する必要があるその他の落とし穴にはどのようなものがあるのでしょうか。

私の質問の動機は、ライブラリでディスパッチ ブロックを見つけ、自分のコードで構成を使用できるようにすることです。したがって、詳細な回答が役立ちます。

!!報奨金を獲得するために!!

わかりましたので、Steve からの回答 (およびコメント) によると、ADL と SFINAE は、コンパイル時にディスパッチを配線するための重要な構成要素です。私は ADL (基本的に) と SFINAE (これも初歩的) の周りに頭を抱えています。しかし、私が思うように、彼らがどのように組織化されているかはわかりません。

オブジェクト内のユーザー提供のメンバー関数を呼び出すか、同じオブジェクトの名前空間で提供されるユーザー提供の自由関数を呼び出すかを、ライブラリがコンパイル時に選択できるように、これら 2 つの構造を組み合わせる方法の例を示したいと思います。これは、上記の 2 つの構成体を使用してのみ行う必要があり、いかなる種類のランタイム ディスパッチも使用しないでください。

問題のオブジェクトが と呼ばれNS::Car、このオブジェクトが の動作をMoveForward(int units)メンバー関数 ofc として提供する必要があるとします。動作がオブジェクトの名前空間から取得される場合、おそらく のようになりますMoveForward(const Car & car_, int units)。をディスパッチしたい関数を定義しましょうmover(NS::direction d, const NS::vehicle & v_)。ここで、方向は列挙型で、v_ は の基底クラスですNS::car

4

5 に答える 5

8

ライブラリは実行時にこれを行わず、呼び出し元のコードがコンパイルされるときにコンパイラによってディスパッチが行われます。引数の 1 つと同じ名前空間にあるフリー関数は、「Koenig ルックアップ」と呼ばれることもある「Argument-Dependent Lookup」(ADL) と呼ばれるメカニズムの規則に従って検出されます。

フリー関数またはメンバー関数のいずれかを実装するオプションがある場合、ライブラリがメンバー関数を呼び出すフリー関数のテンプレートを提供していることが原因である可能性があります。次に、オブジェクトが ADL によって同じ名前の関数を提供する場合、テンプレートをインスタンス化するよりも一致するため、最初に選択されます。Space_C0wb0y が言うように、SFINAE を使用してテンプレート内のメンバー関数を検出し、存在するかどうかに応じて異なることを行う可能性があります。

std::cout << x;にメンバー関数を追加しての動作を変更することはできないため、x何を意味しているのかよくわかりません。

于 2011-03-14T14:25:01.777 に答える
2

さて、コンパイル時に特定の名前 (および署名) のメンバー関数の存在を検出する方法を説明できます。私の友人はここでそれを説明しています:

コンパイル時のメンバー関数の存在の検出

ただし、それは静的タイプでのみ機能するため、目的の場所には到達しません。「車両への参照」を渡したいので、動的型 (参照の背後にある具体的なオブジェクトの型) にそのようなメンバー関数があるかどうかをテストする方法はありません。

ただし、静的タイプで解決する場合は、非常によく似た別の方法があります。「ユーザーがオーバーロードされた無料の関数を提供する場合はそれを呼び出し、そうでない場合はメンバー関数を呼び出してみます」を実装します。そして、それは次のようになります:

namespace your_ns {

template <class T>
void your_function(T const& t)
{
    the_operation(t); // unqualified call to free function
}

// in the same namespace, you provide the "default"
// for the_operation as a template, and have it call the member function:

template <class T>
void the_operation(T const& t)
{
    t.the_operation();
}

} // namespace your_ns

そうすれば、ユーザーは自分のクラスと同じ名前空間で「the_operation」の独自のオーバーロードを提供できるため、ADL によって検出されます。もちろん、ユーザーの「the_operation」は、デフォルトの実装よりも「より専門的」でなければなりません。そうしないと、呼び出しがあいまいになります。ただし、実際には、それは問題ではありません。パラメーターの型を何かへの参照である以上に制限するものはすべて「より特殊化」されるためです。

例:

namespace users_ns {

class foo {};

void the_operation(foo const& f)
{
    std::cout << "foo\n";
}

template <class T>
class bar {};

template <class T>
void the_operation(bar<T> const& b)
{
    std::cout << "bar\n";
}

} // namespace users_ns

編集:Steve Jessopの答えをもう一度読んだ後、それは基本的に彼が書いたものであり、より多くの言葉しかないことに気づきました:)

于 2011-03-24T15:46:15.763 に答える
1

具体的な例を探しているだけの場合は、次のことを考慮してください。

#include <cassert>
#include <type_traits>
#include <iostream>

namespace NS
{
    enum direction { forward, backward, left, right };

    struct vehicle { virtual ~vehicle() { } };

    struct Car : vehicle
    {
        void MoveForward(int units) // (1)
        {
            std::cout << "in NS::Car::MoveForward(int)\n";
        }
    };

    void MoveForward(Car& car_, int units)
    {
        std::cout << "in NS::MoveForward(Car&, int)\n";
    }
}

template<typename V>
class HasMoveForwardMember // (2)
{
    template<typename U, void(U::*)(int) = &U::MoveForward>
    struct sfinae_impl { };

    typedef char true_t;
    struct false_t { true_t f[2]; };

    static V* make();

    template<typename U>
    static true_t check(U*, sfinae_impl<U>* = 0);
    static false_t check(...);

public:
    static bool const value = sizeof(check(make())) == sizeof(true_t);
};

template<typename V, bool HasMember = HasMoveForwardMember<V>::value>
struct MoveForwardDispatcher // (3)
{
    static void MoveForward(V& v_, int units) { v_.MoveForward(units); }
};

template<typename V>
struct MoveForwardDispatcher<V, false> // (3)
{
    static void MoveForward(V& v_, int units) { NS::MoveForward(v_, units); }
};

template<typename V>
typename std::enable_if<std::is_base_of<NS::vehicle, V>::value>::type // (4)
mover(NS::direction d, V& v_)
{
    switch (d)
    {
    case NS::forward:
        MoveForwardDispatcher<V>::MoveForward(v_, 1); // (5)
        break;
    case NS::backward:
        // ...
        break;
    case NS::left:
        // ...
        break;
    case NS::right:
        // ...
        break;
    default:
        assert(false);
    }
}

struct NonVehicleWithMoveForward { void MoveForward(int) { } }; // (6)

int main()
{
    NS::Car v; // (7)
    //NonVehicleWithMoveForward v;  // (8)
    mover(NS::forward, v);
}

HasMoveForwardMember (2)void(V::*)(int)は、指定されたクラスの署名を持つその名前のメンバー関数の存在をチェックするメタ関数ですVMoveForwardDispatcher (3)この情報を使用して、メンバー関数が存在する場合はそれを呼び出し、存在しない場合はフリー関数の呼び出しにフォールバックします。moverの呼び出しMoveForwardMoveForwardDispatcher (5)に委譲するだけです。

投稿されたコードはCar::MoveForward (1)を呼び出しますが、このメンバー関数が削除されたり、名前が変更されたり、署名が変更されたりNS::MoveForwardすると、代わりに呼び出されます。

また、 はテンプレートであるため、 から派生したオブジェクトのみを(4)に渡すmoverことを許可するセマンティクスを保持するために、SFINAE チェックを配置する必要があることに注意してください。実証するために、(7)をコメントアウトして ( 8 )をコメント解除すると、 (6)型のオブジェクトで呼び出されます。NS::vehiclev_ moverNonVehicleWithMoveForward HasMoveForwardMember<NonVehicleWithMoveForward>::value == true

(std::enable_if: 標準ライブラリにおよびが付属していない場合は、代わりにまたはバリアントをstd::is_base_of使用してください。)std::tr1::boost::

この種のコードが通常使用される方法は、常にフリー関数を呼び出し、フリー関数をMoveForwardDispatcher、渡されたオブジェクトのメンバー関数が存在する場合は単純に呼び出すような形でフリー関数を実装することです。オーバーロードを記述する必要はありません。適切なメンバー関数を持つ可能性のあるすべての可能な型のその無料関数。

于 2011-03-29T23:14:19.230 に答える
0

Altought, sometimes, developers can used free functions or class functions, interchangeably, there are some situations, to use one another.

(1) Object / Class functions ("methods), are prefered when most of its purpouse affect only the object, or objects are inteded to compose other objects.

// object method
MyListObject.add(MyItemObject);
MyListObject.add(MyItemObject);
MyListObject.add(MyItemObject);

(2) Free ("global" or "module") functions are prefered, when involves several objects, and the objects are not part / composed of each other. Or, when the function uses plain data (structs without methods, primitive types).

MyStringNamespace.MyStringClass A = new MyStringNamespace.MyStringClass("Mercury");
MyStringNamespace.MyStringClass B = new MyStringNamespace.MyStringClass("Jupiter"); 
// free function
bool X = MyStringNamespace.AreEqual(A, B);

When some common module function access objects, in C++, you have the "friend keyword" that allow them to access the objects methods, without regarding scope.

class MyStringClass {
  private:
    // ...
  protected:
    // ...
  // not a method, but declared, to allow access
  friend:
    bool AreEqual(MyStringClass A, MyStringClass B);
}

bool AreEqual(MyStringClass A, MyStringClass B) { ... }

In "almost pure object oriented" programming languages like Java or C#, where you can't have free functions, free functions are replaced with static methods, which makes stuff more complicated.

于 2011-03-24T17:56:02.167 に答える
0

私が正しく理解していれば、問題は(おそらく複数の)継承を使用して簡単に解決できます。名前空間を使用しない関数がどこかにあります。

namespace NS {
void DoSomething()
{
    std::cout << "NS::DoSomething()" << std::endl;
}
} // namespace NS

同じ関数を転送する基本クラスを使用します。

struct SomethingBase
{
    void DoSomething()
    {
        return NS::DoSomething();
    }
};

SomethingBase から派生したクラス A が DoSomething() 呼び出しを実装していない場合、SomethingBase::DoSomething() を呼び出します -> NS::DoSomething():

struct A : public SomethingBase // probably other bases
{
    void DoSomethingElse()
    {
        std::cout << "A::DoSomethingElse()" << std::endl;
    }
};

SomethingBase から派生した別のクラス B が DoSomething() 呼び出しを実装する場合、それは B::DoSomething() を呼び出します。

struct B : public SomethingBase // probably other bases

{
    void DoSomething()
    {
        std::cout << "B::DoSomething()" << std::endl;
    }
};

そのため、SomethingBase から派生したオブジェクトで DoSomething() を呼び出すと、存在する場合はメンバーが実行され、存在しない場合は free 関数が実行されます。スローするものは何もないことに注意してください。呼び出しに一致するものがない場合、コンパイル エラーが発生します。

int main()
{
    A a;
    B b;
    a.DoSomething(); // "NS::DoSomething()"
    b.DoSomething(); // "B::DoSomething()"
    a.DoSomethingElse(); // "A::DoSomethingElse()"
    b.DoSomethingElse(); // error 'DoSomethingElse' : is not a member of 'B'
}
于 2011-03-30T00:38:22.270 に答える