Class 存储说明符(static/non 静态)作为模板参数以避免重复代码
Class storage specifier (static/non static) as template argument to avoid duplicate code
假设我有一个通用包装器 class 用于一个名为 worker
的对象,它具有许多方法:
template<typename T>
class Wrapper
{
private:
T m_Worker;
public:
void DoSomeWork1() { m_Worker.Work1(); }
void DoSomeWork2() { m_Worker.Work2(); }
// ...
};
现在我可以通过创建一个对象并调用一个成员来简单地使用我的 Wrapper
class:
Wrapper<MyWorker> wrap;
wrap.DoSomeWork1();
但除了“本地”包装器 class,我还需要能够使用“静态”包装器 class (util class),以便能够在不先创建本地对象的情况下调用:
StaticWrapper<MyWorker>::DoSomeWork1();
StaticWrapper<T>
与 Wrapper<T>
相同,但静态实现:
template<typename T>
class StaticWrapper
{
private:
static T& Get()
{
static T worker;
return worker;
}
public:
static void DoSomeWork1(); { Get().Work1(); }
static void DoSomeWork2(); { Get().Work1(); }
// ...
};
问题
上述解决方案使用两种不同的 classes 是可行的,但我必须将相同的代码编写两次,一次使用 static 关键字,一次不使用它。
是否有任何方法或设计模式可以避免两次编写相同的代码,即在一个名为 Wrapper
的 class 中有一个静态和非静态接口,然后决定使用哪个(是否静态)在编译期间创建/使用包装器时?本质上类似于将存储说明符设置为模板参数:
// Fantasy code
// To use a local Wrapper
Wrapper<Locally, MyWorker> localWrap;
// To use a static Wrapper
Wrapper<Statically, MyWorker>::DoSomeWork1();
另请注意: DoSomeWork1(), DoSomeWork2(), ...
方法中的代码比仅从 MyWorker
调用方法更多。所以直接使用静态(或不使用)MyWorker
不是一种选择。
让我们在稍微低一点的层面上分解一下:
template<typename T>
class NonStaticWrapper
{
private:
T m_Worker;
public:
T &Get() { return m_worker; }
};
然后就拿你已经写好的包装器:
template<typename T>
class StaticWrapper
{
private:
static T& Get()
{
static T worker;
return worker;
}
};
现在您可以简单地实现包装器,如下所示:
template<typename T>
class Wrapper : private T
{
public:
void DoSomeWork1() { this->Get().Work1(); }
void DoSomeWork2() { this->Get().Work2(); }
// ...
};
然后实例化 Wrapper<NonStaticWrapper<T>>
或 Wrapper<StaticWrapper<T>>
(1).
对于 Wrapper
,您可以选择也可以不选择使用 public 或私有继承。构造 (2) 可能需要一些额外的工作。但这些只是次要的细节。
(1) this->Get()
或 T::Get()
在这里通常是等效的,这是一个偏好问题。
(2) 你可能需要使用类似
Wrapper<StaticWrapper<T>>{}.DoSomeWork1();
调用静态版本。
您可以保留静态 API 而不是 T 本身,而是 Wrapper。
带有“static API”的结果 class 可能如下所示:
template <typename T>
class StaticWrapper {
private:
static Wrapper<T>& Get() {
static Wrapper<T> worker;
return worker;
}
public:
static void DoSomeWork1() { Get().DoSomeWork1(); }
static void DoSomeWork2() { Get().DoSomeWork2(); }
// ...
};
问题更新:
另一种更好的方法(没有单独的 StaticWrapper class)
template <typename T>
class Wrapper {
private:
T m_Worker;
public:
// Standard API to be implemented
void DoSomeWork1() { m_Worker.Work1(); }
void DoSomeWork2() { m_Worker.Work2(); }
// Static API just wraps Standard API
static void DoSomeWork1_Static() {
static Wrapper<T> x;
x.DoSomeWork1();
}
static void DoSomeWork2_Static() {
static Wrapper<T> x;
x.DoSomeWork2();
}
};
void call_examples() {
Wrapper<w1> w1;
w1.DoSomeWork1();
Wrapper<w1>::DoSomeWork1_Static();
Wrapper<w2>::DoSomeWork2_Static();
}
(您可以在下面找到原始答案和方法)
的优点最多,可以扩展到你想要的。使用 Wrapper
本身作为静态成员并委托给它。要获得您想要的语法,您只需使用“虚拟策略”参数和一些模板专门化来构建代码。
struct MemberData;
struct StaticData;
template<typename T, typename StoragePolicy = MemberData>
class Wrapper;
template<typename T>
class Wrapper<T, MemberData>
{
private:
T m_Worker;
public:
void DoSomeWork1() { m_Worker.Work1(); }
void DoSomeWork2() { m_Worker.Work2(); }
// ...
};
template<typename T>
class Wrapper<T, StaticData>
{
private:
static auto& get() {
static Wrapper<T, MemberData> obj;
return obj;
}
public:
static void DoSomeWork1() { get().DoSomeWork1(); }
static void DoSomeWork2() { get().DoSomeWork2(); }
// ...
};
// ...
int main(int, char **)
{
//Wrapper<MyWorker>::DoSomeWork1(); //error
Wrapper<MyWorker> wrap;
wrap.DoSomeWork1();
Wrapper<MyWorker, StaticData>::DoSomeWork1();
}
总而言之,这要简单得多,并且很好地避免了重复实现。仍然需要一些样板来获得等效的成员集。
您可以通过一些重组和基于策略的设计来获得您想要的东西。但是样板的数量可能不值得。
我们可以将变量(及其)关联成员函数的存储抽象为策略(在本例中,表示为模板模板参数,但它可以是类型参数)。然后 Wrapper
公开策略的成员,并提供该实现(作为接受数据进行操作的静态成员)。
template<class Worker, template<class> class Storage>
class Wrapper;
template<class Worker>
class MemberData {
Worker m_Worker;
protected:
void DoSomeWork1() { Wrapper<Worker, ::MemberData>::DoSomeWork1Impl(m_Worker); }
void DoSomeWork2() { Wrapper<Worker, ::MemberData>::DoSomeWork2Impl(m_Worker); }
};
template<class Worker>
class StaticData {
static auto& get() {
static Worker worker;
return worker;
}
protected:
static void DoSomeWork1() { Wrapper<Worker, ::StaticData>::DoSomeWork1Impl(get()); }
static void DoSomeWork2() { Wrapper<Worker, ::StaticData>::DoSomeWork2Impl(get()); }
};
template<class Worker, template<class> class Storage = MemberData>
class Wrapper : private Storage<Worker> {
friend class Storage<Worker>;
static void DoSomeWork1Impl(Worker&) { /*...*/ }
static void DoSomeWork2Impl(Worker&) { /*...*/ }
public:
using Storage<Worker>::DoSomeWork1;
using Storage<Worker>::DoSomeWork2;
};
int main(int, char **)
{
//Wrapper<int>::DoSomeWork1(); //error
Wrapper<int> wrap;
wrap.DoSomeWork1();
Wrapper<int, StaticData>::DoSomeWork1();
}
正如您可以从一个简单示例的此实现的复杂性中判断的那样,“条件静态”很难实现。如果 DoSomeWork1Impl
依赖于 Wrapper
中的数据,那么它将变得更加复杂。
假设我有一个通用包装器 class 用于一个名为 worker
的对象,它具有许多方法:
template<typename T>
class Wrapper
{
private:
T m_Worker;
public:
void DoSomeWork1() { m_Worker.Work1(); }
void DoSomeWork2() { m_Worker.Work2(); }
// ...
};
现在我可以通过创建一个对象并调用一个成员来简单地使用我的 Wrapper
class:
Wrapper<MyWorker> wrap;
wrap.DoSomeWork1();
但除了“本地”包装器 class,我还需要能够使用“静态”包装器 class (util class),以便能够在不先创建本地对象的情况下调用:
StaticWrapper<MyWorker>::DoSomeWork1();
StaticWrapper<T>
与 Wrapper<T>
相同,但静态实现:
template<typename T>
class StaticWrapper
{
private:
static T& Get()
{
static T worker;
return worker;
}
public:
static void DoSomeWork1(); { Get().Work1(); }
static void DoSomeWork2(); { Get().Work1(); }
// ...
};
问题
上述解决方案使用两种不同的 classes 是可行的,但我必须将相同的代码编写两次,一次使用 static 关键字,一次不使用它。
是否有任何方法或设计模式可以避免两次编写相同的代码,即在一个名为 Wrapper
的 class 中有一个静态和非静态接口,然后决定使用哪个(是否静态)在编译期间创建/使用包装器时?本质上类似于将存储说明符设置为模板参数:
// Fantasy code
// To use a local Wrapper
Wrapper<Locally, MyWorker> localWrap;
// To use a static Wrapper
Wrapper<Statically, MyWorker>::DoSomeWork1();
另请注意: DoSomeWork1(), DoSomeWork2(), ...
方法中的代码比仅从 MyWorker
调用方法更多。所以直接使用静态(或不使用)MyWorker
不是一种选择。
让我们在稍微低一点的层面上分解一下:
template<typename T>
class NonStaticWrapper
{
private:
T m_Worker;
public:
T &Get() { return m_worker; }
};
然后就拿你已经写好的包装器:
template<typename T>
class StaticWrapper
{
private:
static T& Get()
{
static T worker;
return worker;
}
};
现在您可以简单地实现包装器,如下所示:
template<typename T>
class Wrapper : private T
{
public:
void DoSomeWork1() { this->Get().Work1(); }
void DoSomeWork2() { this->Get().Work2(); }
// ...
};
然后实例化 Wrapper<NonStaticWrapper<T>>
或 Wrapper<StaticWrapper<T>>
(1).
对于 Wrapper
,您可以选择也可以不选择使用 public 或私有继承。构造 (2) 可能需要一些额外的工作。但这些只是次要的细节。
(1) this->Get()
或 T::Get()
在这里通常是等效的,这是一个偏好问题。
(2) 你可能需要使用类似
Wrapper<StaticWrapper<T>>{}.DoSomeWork1();
调用静态版本。
您可以保留静态 API 而不是 T 本身,而是 Wrapper。
带有“static API”的结果 class 可能如下所示:
template <typename T>
class StaticWrapper {
private:
static Wrapper<T>& Get() {
static Wrapper<T> worker;
return worker;
}
public:
static void DoSomeWork1() { Get().DoSomeWork1(); }
static void DoSomeWork2() { Get().DoSomeWork2(); }
// ...
};
问题更新: 另一种更好的方法(没有单独的 StaticWrapper class)
template <typename T>
class Wrapper {
private:
T m_Worker;
public:
// Standard API to be implemented
void DoSomeWork1() { m_Worker.Work1(); }
void DoSomeWork2() { m_Worker.Work2(); }
// Static API just wraps Standard API
static void DoSomeWork1_Static() {
static Wrapper<T> x;
x.DoSomeWork1();
}
static void DoSomeWork2_Static() {
static Wrapper<T> x;
x.DoSomeWork2();
}
};
void call_examples() {
Wrapper<w1> w1;
w1.DoSomeWork1();
Wrapper<w1>::DoSomeWork1_Static();
Wrapper<w2>::DoSomeWork2_Static();
}
(您可以在下面找到原始答案和方法)
Wrapper
本身作为静态成员并委托给它。要获得您想要的语法,您只需使用“虚拟策略”参数和一些模板专门化来构建代码。
struct MemberData;
struct StaticData;
template<typename T, typename StoragePolicy = MemberData>
class Wrapper;
template<typename T>
class Wrapper<T, MemberData>
{
private:
T m_Worker;
public:
void DoSomeWork1() { m_Worker.Work1(); }
void DoSomeWork2() { m_Worker.Work2(); }
// ...
};
template<typename T>
class Wrapper<T, StaticData>
{
private:
static auto& get() {
static Wrapper<T, MemberData> obj;
return obj;
}
public:
static void DoSomeWork1() { get().DoSomeWork1(); }
static void DoSomeWork2() { get().DoSomeWork2(); }
// ...
};
// ...
int main(int, char **)
{
//Wrapper<MyWorker>::DoSomeWork1(); //error
Wrapper<MyWorker> wrap;
wrap.DoSomeWork1();
Wrapper<MyWorker, StaticData>::DoSomeWork1();
}
总而言之,这要简单得多,并且很好地避免了重复实现。仍然需要一些样板来获得等效的成员集。
您可以通过一些重组和基于策略的设计来获得您想要的东西。但是样板的数量可能不值得。
我们可以将变量(及其)关联成员函数的存储抽象为策略(在本例中,表示为模板模板参数,但它可以是类型参数)。然后 Wrapper
公开策略的成员,并提供该实现(作为接受数据进行操作的静态成员)。
template<class Worker, template<class> class Storage>
class Wrapper;
template<class Worker>
class MemberData {
Worker m_Worker;
protected:
void DoSomeWork1() { Wrapper<Worker, ::MemberData>::DoSomeWork1Impl(m_Worker); }
void DoSomeWork2() { Wrapper<Worker, ::MemberData>::DoSomeWork2Impl(m_Worker); }
};
template<class Worker>
class StaticData {
static auto& get() {
static Worker worker;
return worker;
}
protected:
static void DoSomeWork1() { Wrapper<Worker, ::StaticData>::DoSomeWork1Impl(get()); }
static void DoSomeWork2() { Wrapper<Worker, ::StaticData>::DoSomeWork2Impl(get()); }
};
template<class Worker, template<class> class Storage = MemberData>
class Wrapper : private Storage<Worker> {
friend class Storage<Worker>;
static void DoSomeWork1Impl(Worker&) { /*...*/ }
static void DoSomeWork2Impl(Worker&) { /*...*/ }
public:
using Storage<Worker>::DoSomeWork1;
using Storage<Worker>::DoSomeWork2;
};
int main(int, char **)
{
//Wrapper<int>::DoSomeWork1(); //error
Wrapper<int> wrap;
wrap.DoSomeWork1();
Wrapper<int, StaticData>::DoSomeWork1();
}
正如您可以从一个简单示例的此实现的复杂性中判断的那样,“条件静态”很难实现。如果 DoSomeWork1Impl
依赖于 Wrapper
中的数据,那么它将变得更加复杂。