如何使用c++元编程在编译时包装和重载所有成员函数(来自未知类)



假设我们有一个class A(不允许修改(,并希望用新的粘合代码包装所有成员函数来决定行为。

struct A {
int foo(){ return a; }
int bar(){ return b; }
int a = 1;
int b = 2;
}

只有在满足条件(即指针不为空(的情况下,我们才能包装任何类T(可能有多个方法(以调用T

template<class T>
struct Wrapped {
Wrapped(T* t): _t(t){ }
int foo(){ return (_t == nullptr ? 0 : _t->foo()); }
int bar(){ return (_t == nullptr ? 0 : _t->bar()); }
T* _t;
}

用例是,我们可以像操作A一样安全地操作Wrapped<A>

auto a1 = A();
a1.foo(); // 1
auto a2 = Wrapped<A>(new A());
a2.foo(); // 1
auto a3 = Wrapped<A>(nullptr);
a3.foo(); // 0

其他注意事项:

  • 我们不想为每个可能被包装的类编写一个新的专用Wrapper
  • 我知道存在类似shared_ptr<>的东西,但我正试图在访问类和方法调用本身之间插入自定义行为
  • 我希望这在性能方面尽可能地轻量级
  • 如果可能,没有巨大的依赖(即Boost(

如有任何建议或指向正确的方向,我们将不胜感激

C++目前缺乏所需的反射机制来实现这样的东西,以满足您的所有需求。

无法获得类的成员函数名列表,在不知道成员函数名的情况下"转发"成员函数调用的唯一方法是operator->,就像标准库智能指针一样,显然您认为这不足以满足您的目的。

您需要修改原始类本身以提供反射信息,或者需要单独为每个类编写包装器(或者至少列出所有可能的成员函数名(。

目前有一个反射TS(技术规范,即C++的实验性扩展(的草案,它允许反映和获得成员函数的名称,但据我所知,它仍然不允许使用这些反射的名称来声明具有这些名称的实体,因此仍然不足以解决您的用例。

B.Stroustrup有一篇文章,"包装C++成员函数调用",描述了一种通用的方法

本文提出了一个简单、通用、有效的解决方案,以解决"用前缀和后缀代码对对象进行"rapping"调用的旧问题。该解决方案也是非侵入性的,适用于现有的类,允许使用几个前缀/后缀对,〔…〕

IIRC,Stroustrup的包装器有点像智能指针,它在operator->()中运行代码,并在运行包装对象的函数后运行其他一些代码(有关详细信息,请参阅文章(。这种方法有几个限制,请参阅文章末尾。

根据您的具体需求,您可能可以使用这种方法。在您的示例中,如果包装为nullptr,则包装器只是在每个函数中返回0,因此它可能会工作。对于更复杂的行为,可能不会。

更新:查看@Jarod42的答案。Stroustrup的包装器是一个更复杂的版本。对于您的案例/示例,当包装nullptr时,operator->()可能会返回一个指向其他给定固定实例的指针。

在您的情况下并不理想,但operator->可能会有所帮助:

template<class T>
struct Wrapped {
Wrapped(T* t): _t(t){ }
const T* operator -> () const { if (_t == nullptr) throw std::logic_error("null pointer"); return _t; }
T* operator -> () { if (_t == nullptr) throw std::logic_error("null pointer"); return _t; }
T* _t;
};

它转发给任何成员。它允许在呼叫之前或之后(具有额外的RAII(具有代码。

您可以考虑在std::optional<T>:中使用可选值

const A defaultA{ 0, 0 };   // set the default values that must be returned
std::optional<A> foo(int x)
{
auto opt = std::optional<A>();
if (x >= 0)
opt.emplace();
return opt;
}
int main()
{
auto a1 = foo(-1);      // returns an optional with no value
std::cout << a1.value_or(defaultA).foo() << std::endl; // 0 (foo is called on defaultA)
std::cout << (a1?a1->bar():0)            << std::endl; // 0
auto a2 = foo(4);       // returns an optional with a value
std::cout << a2.value_or(defaultA).foo() << std::endl; // 1 
std::cout << a2.value_or(defaultA).bar() << std::endl; // 2
}

有关std::optional<T>的更多信息:https://en.cppreference.com/w/cpp/utility/optional

希望这能有所帮助。

最新更新