我有自己的数组类模板,我想可选择添加功能。
作为功能的一个例子,采用多线程支持:在某些情况下,我需要在任何更新代码之前放置#pragma omp atomic
的数组(强制执行原子行为的编译器指令,细节并不重要)。在其他情况下,我需要不执行此操作的数组,因为我知道它们只会安全更新,我需要避免性能损失。
直观地说,应该可以定义一个我可以继承的名为AtomicUpdates
的类。所以要定义一个带有原子更新的双数组,我会说像
class AtomicDoubleArray : public MyArray<double>, public AtomicUpdates {};
但是我看不出你在实践中如何实现它,这也会破坏继承接口的原则,而不是实现。
任何人都可以告诉我我在这里真正想做什么吗?
答案 0 :(得分:5)
即使您最终没有使用它们,现在使用mixins和策略模板参数是非常有用的事情。在这种情况下,它们非常相似。首先,一个带有mixin base的数组。我使用过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的重要区别在于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的优势很有用,但并不常见。因此,在使用模板时,策略方法通常更合适。标准库在许多地方使用策略参数,因此有一些很好的例子供您学习。
关于“继承接口,而不是实现”的问题。使用仔细继承实现是非常有用的。多重继承也是如此。你只需要明智地使用它们。