10

包含标准的大量 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"

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

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

!!赢得赏金!!

好的,根据史蒂夫的回答(和评论),ADL 和 SFINAE 是在编译时连接调度的关键结构。我的头围绕着 ADL(最初)和 SFINAE(再次粗略)。但我不知道他们如何以我认为他们应该的方式一起编排。

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

假设有问题的对象被调用NS::Car,并且该对象需要提供 的行为MoveForward(int units)作为 c 的成员函数。如果要从对象的命名空间中获取行为,它可能看起来像MoveForward(const Car & car_, int units). 让我们定义要调度的函数mover(NS::direction d, const NS::vehicle & v_),其中方向是枚举,v_ 是 的基类NS::car

4

5 回答 5

8

该库在运行时不执行任何此操作,分派由编译器在编译调用代码时完成。根据称为“Argument-Dependent Lookup”(ADL)的机制的规则,可以找到与参数之一在同一名称空间中的自由函数,有时称为“Koenig 查找”。

如果您可以选择实现自由函数或成员函数,这可能是因为库为调用成员函数的自由函数提供了模板。那么如果你的对象通过 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

编辑:再次阅读史蒂夫杰索普的回答后,我意识到这基本上是他写的,只是有更多的话:)

于 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则会改为调用。

另请注意,由于mover是模板,因此必须进行 SFINAE 检查以保留仅允许从(4)NS::vehicle中派生的对象传入的语义。为了证明,如果注释掉(7)和取消注释(8),将使用类型(6)的对象调用,尽管事实上.v_ moverNonVehicleWithMoveForward HasMoveForwardMember<NonVehicleWithMoveForward>::value == true

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

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

于 2011-03-29T23:14:19.230 回答
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# 等“几乎纯面向对象”的编程语言中,您不能拥有自由函数,自由函数被静态方法取代,这使事情变得更加复杂。

于 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() 将执行该成员(如果存在),否则将执行自由函数。请注意,没有什么可抛出的,如果与您的调用不匹配,则会出现编译错误。

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 回答