C ++ 03中后期绑定接口的设计模式

时间:2011-04-18 17:47:12

标签: c++ design-patterns interface crtp

在某些情况下,您希望将类用作接口的实现,但您不能 修改代码或封装此类(例如从中派生)。在其他情况下,您可能想要 抢先出现这种情况。 // 让我们假设以下示例,我们有以下“密封”类,我们不能 改变代码:

class my_sealed_class;
class my_lock 
{
    friend class my_sealed_class;
    my_lock() {}
    my_lock(my_lock const&) {}
};

class my_sealed_class : virtual my_lock
{
private:
    int x;

public:
    void set_x(int val) { x = val; }
    int  get_x() const  { return x; }
};

基本上有两种设计模式可供您选择: 1.)实现作为接口的成员对象。

template<class implementation>
class my_interface0
{
private:
    implementation m_impl;

public:
    void set_x(int val) { m_impl.set_x(val); }
    int  get_x() const  { return m_impl.get_x(); }
};

由于C ++ 03模板的功能,这是非常类型安全的。许多编译器都会内联 实际上称为'实现'的成员函数 - 不幸的是,不是每个编译器。在一些 我们必须处理不必要的电话。

2。)使用CRTP模式而不从基础派生。

template<class derived>
class my_interface1
{
public:
    void set_x(int val) { reinterpret_cast<derived*>(this)->set_x(val); }
    int  get_x() const  { return reinterpret_cast<derived const*>(this)->get_x(); }
};

是的,我知道:这不是使用CRTP模式的正常方式。通常我们会定义一些东西 那样:

template<class derived> class base { void foo() { static_cast<derived*>(this)->foo(); } };
class derived : base<derived>      { void foo() {} };

不幸的是,我们不能这样做。请记住,我们无法更改实现类的代码。 让我们考虑使用my_interface1和我的CRTP模式版本到底发生了什么。在 编程语言C ++ 03的理论,可以安全地将派生类指针强制转换为其中一个 基类。另一方面,在相反方向(从基地到第二方向)施放是不安全的 衍生的)或完全不同的类型。无法保证内存对象已保留 基本和派生类所需的字节数。 但是,在实践中,这不属于这个例子,因为我们的接口不包含任何 会员。因此,它的大小为0字节(请注意,new new确实至少分配了1个字节 当班级为空时)。在这种情况下,几乎可以安全地将任何类型指针强制转换为a 'my_interface1指针。决定性的优点是,几乎每个编译器都会内联对实际调用的成员函数的调用。

int main()
{
    // Not even worth to mention: That's safe.
    my_sealed_class msc;
    msc.set_x(1);

    // Safe, because 'my_interface0' instantiates 'my_sealed_class'.
    my_interface0<my_sealed_class> mi0;
    mi0.set_x(2);

    // Absolutely unsafe, because 'my_interface1' will access memory which wasn't allocated by it.
    my_interface1<my_sealed_class> mi1;
    mi1.set_x(3);

    // Safe, because 'my_interface1*' references an my_sealed_class object.
    my_interface1<my_sealed_class>* pmi1 = reinterpret_cast<my_interface1<my_sealed_class>*>(&msc);
    pmi1->set_x(4);

    return 0;
}

那么,您认为最佳做法是什么? 最好的问候。

2 个答案:

答案 0 :(得分:2)

在我看来,选项二具有未定义的行为违反了严格的别名规则,这可能会很好地禁用某些优化或破坏您的代码。此外,任何进入并阅读代码的人几乎肯定会对其工作方式感到困惑。

第一个例子是有效代码,这是大多数程序员都能理解的标准模式,应该由几乎任何编译器内联。我肯定会推荐这种方法。即使在一些编译器无法内联传递调用的奇怪世界中,只有在分析显示额外调用是一个重大瓶颈时,我才会考虑采用其他方法来提高性能。

编辑:回答最初问题的最后一个问题:在选项1中使用选项2将永远是最佳做法。如果选项1存在性能问题,可能有更好的方法解决它们而不是未定义的行为。

答案 1 :(得分:0)

因此,如果我理解,您不能使用继承(派生自my_sealed_class),因为它的方法未声明为虚拟,您无法更改它。你试过私人继承吗?

在这种情况下,我会选择(1)组合,因为它更简单,更容易阅读,编译器可以很好地优化它。我也认为它是STL的作用。

(2)只有看起来更快,那些重新解释演员表不会那么容易优化,而且代码有点难以阅读。