通过自由函数或成员函数扩展的机制

时间:2011-03-14 14:19:27

标签: c++ oop templates compile-time library-design

包含标准的大量C ++库允许您调整对象以便在库中使用。选择通常在成员函数或同一名称空间中的自由函数之间。

我想知道机制并构造库代码用来调用一个调用这个“扩展”函数之一的调用,我知道这个决定必须在编译期间进行,并涉及模板。以下运行时伪代码是不可能/无意义的,原因超出了这个问题的范围。

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"

上面的代码看起来像运行时代码:/。那么,库如何找出一个类所在的命名空间,它如何检测这三个条件,还有哪些需要避免的陷阱。

我的问题的动机是让我能够在库中找到调度块,并能够在我自己的代码中使用这些构造。所以,详细的答案会有所帮助。

!!赢得胜利!!

好的,根据Steve(和评论)的回答,ADL和SFINAE是在编译时连接调度的关键结构。我的头部是ADL(原始)和SFINAE(再次是粗鲁的)。但我不知道他们是如何以我认为应该的方式共同组织的。

我想看一个如何将这两个结构组合在一起的说明性示例,以便库可以在编译时选择是在对象中调用用户提供的成员函数,还是在用户提供的用户提供的自由函数中选择对象的命名空间。这应该只使用上面的两个构造来完成,没有任何类型的运行时分派。

让我们说有问题的对象叫做NS::Car,这个对象需要提供MoveForward(int units)的行为,作为c的成员函数。如果要从对象的名称空间中拾取行为,它可能看起来像MoveForward(const Car & car_, int units)。让我们定义想要调度mover(NS::direction d, const NS::vehicle & v_)的函数,其中direction是枚举,而v_是NS::car的基类。

5 个答案:

答案 0 :(得分:8)

库在运行时不执行任何操作,在编译调用代码时由编译器完成调度。根据称为“参数依赖查找”(ADL)的机制的规则,找到与其中一个参数相同的命名空间中的自由函数,有时称为“Koenig查找”。

如果您可以选择实现自由函数或成员函数,则可能是因为库为调用成员函数的自由函数提供了模板。然后,如果您的对象通过ADL提供相同名称的函数,那么它将比实例化模板更好地匹配,因此将首先选择。正如Space_C0wb0y所说,他们可能会使用SFINAE来检测模板中的成员函数,并根据它是否存在来做不同的事情。

您无法通过向std::cout << x;添加成员函数来更改x的行为,因此我不太清楚您的意思。

答案 1 :(得分:2)

好吧,我可以告诉你如何在编译时检测某个名称(和签名)的成员函数的存在。我的一个朋友在这里描述了它:

Detecting the Existence of Member Functions at Compile-Time

然而,这不会让你到达目的地,因为它只适用于静态类型。由于您想要传递“车辆参考”,因此无法测试动态类型(引用后面的具体对象的类型)是否具有此类成员函数。

如果你满足于静态类型,还有另一种方法可以做一个非常相似的事情。 它实现了“如果用户提供了一个重载的自由函数,则调用它,否则尝试调用成员函数”。它是这样的:

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的回答之后,我意识到这基本上就是他写的,只有更多的话:)

答案 2 :(得分: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)中是否存在具有签名V的该名称的成员函数。 MoveForwardDispatcher (3)使用此信息调用成员函数(如果存在)或回退到调用自由函数(如果不存在)。 mover只需将MoveForward的调用委托给MoveForwardDispatcher (5)

as-posted代码将调用Car::MoveForward (1),但如果删除,重命名此成员函数或更改其签名,则会调用NS::MoveForward代替。

另请注意,由于mover是模板,因此必须执行SFINAE检查以保留仅允许从NS::vehicle派生的对象传递给v_ <的语义EM>(4)。为了演示,如果有人注释(7)并取消注释(8),将使用mover NonVehicleWithMoveForward >(6),尽管有HasMoveForwardMember<NonVehicleWithMoveForward>::value == true

,但我们还是要禁止

注意:如果您的标准库未附带std::enable_ifstd::is_base_of,请使用std::tr1::boost::变体代替。)

通常使用这种代码的方式是始终调用自由函数,并使用类似MoveForwardDispatcher的函数实现自由函数,这样自由函数只调用传入的对象的成员函数,如果它存在,而不必为可能具有适当成员函数的每种可能类型编写该自由函数的重载。

答案 3 :(得分:0)

Altought,有时候,开发人员可以使用自由函数或类函数,可以互换,有些情况,互相使用。

(1)当对象/类函数(“方法”)的大多数只影响对象,或者对象打算组成其他对象时,它们是首选。

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

(2)当涉及多个对象时,首选自由(“全局”或“模块”)函数,并且对象不是彼此的部分/组合。或者,当函数使用纯数据时(没有方法的结构,基本类型)。

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

当一些常见的模块函数访问对象时,在C ++中,你有“friend关键字”,允许他们访问对象方法,而不考虑范围。

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) { ... }

在“几乎纯粹面向对象”的编程语言中,如Java或C#,你不能拥有自由函数,自由函数被静态方法取代,这使得事情变得更加复杂。

答案 4 :(得分:0)

如果我理解正确,您的问题可以通过使用(可能是多个)继承来解决。你有一个名称空间自由函数:

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

使用转发相同功能的基类:

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

如果派生自SomethingBase的某个类没有实现DoSomething()调用它将调用SomethingBase :: DoSomething() - &gt; 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'
}