这是一个混合模式吗?它能够在C++中实现吗?

5

我有自己的数组类模板,希望可以选择性地添加功能。

举个例子,比如多线程支持的功能:在某些情况下,我需要数组在任何更新代码之前加入 #pragma omp atomic(这是一种强制原子行为的编译器指令,细节并不重要)。而在其他情况下,我需要的数组则不需要这样做,因为我知道它们只会被安全地更新,并且我需要避免性能损失。

直觉上,应该可以定义一个名为 AtomicUpdates 的类来继承。因此,如果我想定义一个具有原子更新的双精度数组,我应该这样说:

class AtomicDoubleArray : public MyArray<double>, public AtomicUpdates {};

但我不明白如何在实践中实现它,而且这会违反“继承接口而非实现”的原则。

有人能为我解惑吗?究竟我在这里想要做什么?


2
听起来像是给你的 MyArray 模板添加一个策略参数可能会更好。 - Jerry Coffin
1个回答

5

即使您现在不使用它们,混合和策略模板参数也是非常有用的东西。在这种情况下,它们非常相似。首先,使用混合基础的数组。我使用了C++0x互斥锁而不是OpenMP,但您应该能够理解。

#include <iostream>
#include <vector>
#include <mutex>

template <class value_t, class base_t>
class array_t : private base_t {
    std::vector<value_t> v_;
public:
    array_t(size_t sz = 0) : v_ (sz) { }
    value_t get(size_t i) const
    {
        this->before_get();
        value_t const result = v_[i];
        this->after_get();
        return result;
    }
    void set(size_t i, value_t const& x)
    {
        this->before_set();
        v_[i] = x;
        this->after_set();
    }
};

class no_op_base_t {
protected:
    void before_get() const { }
    void after_get() const { }
    void before_set() const { }
    void after_set() const { }
};

class lock_base_t {
    mutable std::mutex m_;
protected:
    void before_get() const { std::cout << "lock\n"; m_.lock(); }
    void after_get() const { std::cout << "unlock\n"; m_.unlock(); }
    void before_set() const { std::cout << "lock\n"; m_.lock(); }
    void after_set() const { std::cout << "unlock\n"; m_.unlock(); }

};

int main()
{
    array_t<double, no_op_base_t> a (1);
    array_t<double, lock_base_t> b (1);
    std::cout << "setting a\n";
    a.set(0, 1.0);
    std::cout << "setting b\n";
    b.set(0, 1.0);
    std::cout << "getting a\n";
    a.get(0);
    std::cout << "getting b\n";
    b.get(0);
    return 0;
}

现在我们使用策略参数方法来实现同样的类,而不是继承。

#include <iostream>
#include <vector>
#include <mutex>

template <class value_t, class policy_t>
class array_t {
    policy_t policy_;
    std::vector<value_t> v_;
public:
    array_t(size_t sz = 0) : v_ (sz) { }
    value_t get(size_t i) const
    {
        policy_.before_get();
        value_t const result = v_[i];
        policy_.after_get();
        return result;
    }
    void set(size_t i, value_t const& x)
    {
        policy_.before_set();
        v_[i] = x;
        policy_.after_set();
    }
};

class no_op_base_t {
public:
    void before_get() const { }
    void after_get() const { }
    void before_set() const { }
    void after_set() const { }
};

class lock_base_t {
    mutable std::mutex m_;
public:
    void before_get() const { std::cout << "lock\n"; m_.lock(); }
    void after_get() const { std::cout << "unlock\n"; m_.unlock(); }
    void before_set() const { std::cout << "lock\n"; m_.lock(); }
    void after_set() const { std::cout << "unlock\n"; m_.unlock(); }

};

int main()
{
    array_t<double, no_op_base_t> a (1);
    array_t<double, lock_base_t> b (1);
    std::cout << "setting a\n";
    a.set(0, 1.0);
    std::cout << "setting b\n";
    b.set(0, 1.0);
    std::cout << "getting a\n";
    a.get(0);
    std::cout << "getting b\n";
    b.get(0);
    return 0;
}

在这种情况下,两者非常相似。重要的区别在于Mixin可以定义一些方法为虚拟方法,并允许您通过继承它来更改数组的行为。如下所示:
template <class value_t>
class mk_virtual_base_t {
protected:
    void before_get() const { }
    void after_get() const { }
    void before_set() const { }
    void after_set() const { }

    virtual value_t get(size_t) const = 0;
    virtual void set(size_t, value_t) = 0;
};

template <class value_t>
class daily_wtf_contender_t : public array_t<value_t, mk_virtual_base_t<value_t> > {
    virtual value_t get(size_t) const { std::cout << "surprise! get is virtual!\n"; return 0; }
    virtual void set(size_t, value_t) { std::cout << "surprise! set is virtual!\n"; }
};

虽然mixin有其优点,但实际应用并不是那么频繁。因此,在处理模板时,策略方法通常更为适用。标准库在许多地方使用策略参数,因此有一些很好的示例供您学习。

至于您关于“继承接口而非实现”的问题。谨慎使用继承实现是非常有用的。多重继承也是如此。您只需要在使用它们时审慎选择。


1
还有一个重要的区别,即只有第一种代码允许编译器执行EBO - 空基类优化。由于许多策略类没有数据成员,因此这通常会使派生类更小,可能会改善后续数据成员的内存对齐。这就是为什么标准库实现和诸如Boost之类的库中的大多数(全部?)策略都是私有继承,即使它们不使用虚函数也是如此的原因。 - Konrad Rudolph
谢谢,现在清晰多了。但是为什么策略方法不能定义虚拟方法呢? - Sideshow Bob
它不能定义虚方法,因为它在对象内被静态分配。模板参数是策略对象的最终类型,因为没有办法分配一个派生类型的对象来代替它。 - Bowie Owens

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接