用于定义成员对象方法别名的宏。






4.83/5 (6投票s)
本文展示了定义传递方法的技巧。
介绍
我们经常需要在类中定义一个对象,并希望在类中定义方法来访问前者的某些方法。有时这些方法被称为直通方法。不幸的是,这里使用的方法在 Visual C++ 2012 中不起作用;它们在 GNU C++ 编译器版本 4.7.0 或更高版本中有效,并启用了 std=c++11
选项。
问题的演示
首先,让我们定义一个将用于包含对象的类
typedef std::vector<double> block; class A { block x; public: A(const block& x1):x(x1) {} A(block&& x1):x(std::move(x1)) {} block add(const block& y) { std::cout << "use copy, non-const" << std::endl; std::size_t n = x.size(); for (std::size_t i = 0; i < n; i++) { x[i] += y[i]; } return x; } block add(const block& y) const { std::cout << "use copy, const" << std::endl; std::size_t n = x.size(); block z = y; for (std::size_t i = 0; i < n; i++) { z[i] += x[i]; } return z; } block add(block&& y) const { std::cout << "use move, const" << std::endl; std::size_t n = x.size(); for (std::size_t i = 0; i < n; i++) { y[i] += x[i]; } return std::move(y); } block get() const { return x;} };
我故意给出了一个带有右值引用和一些 const 限定符的例子,以证明所提出的技术适用于各种情况。
现在,让我们定义类本身:
class B { A a; public: // constructors B(const block& x1):a(x1) {} B(block&& x1):a(std::move(x1)) {} // methods block add(const block& y) { return a.add(y); } block add(const block& y) const { return a.add(y);} block add(block&& y) { return a.add(std::move(y));} block get() const { return a.get(); } };
在现实世界中,我们会在类 B
中加入一些额外的功能。 这是程序的其余部分:
// an auxiliary function for printing void print(const std::string& name, const block& v) { std::cout << name; std::size_t n = v.size(); std::cout << "(" << n << "): "; for (std::size_t i = 0; i < n; i++) { std::cout << v[i] << (i == n-1 ? "" : ", "); } std::cout << "\n" << std::endl; } int main() { block z; z.push_back(10.0); z.push_back(20.0); z.push_back(30.0); B b1(z); const B b2(z); const B b3(z); block p; p.push_back(100.0); p.push_back(111.0); p.push_back(3000.0); block r1 = b1.add(p); print("p",p); print("r1",r1); block x1 = b1.get(); print("x1",x1); block r2 = b2.add(p); print("p",p); print("r2",r2); block x2 = b2.get(); print("x2",x2); block r3 = b3.add(std::move(p)); print("p",p); print("r3",r3); block x3 = b3.get(); print("x3",x3); return 0; }
该程序将打印:
use copy, non-const p(3): 100, 111, 3000 r1(3): 110, 131, 3030 x1(3): 110, 131, 3030 use copy, const p(3): 100, 111, 3000 r2(3): 110, 131, 3030 x2(3): 10, 20, 30 use move, const p(0): r3(3): 110, 131, 3030 x3(3): 10, 20, 30
您可以看到,所有这三个 add
函数都会产生相同的结果,但在幕后它们
做的事情不同:第一个更改成员变量 x
,第二个 — 不改变任何东西,第三个 —更改参数(实际上将其“移动”到结果中)。
我们必须编写以下定义:
block add(const block& y) { return a.add(y); } block add(const block& y) const { return a.add(y);} block add(block&& y) { return a.add(std::move(y));} block get() const { return a.get(); }
成员函数的简单别名定义
我们将要使用的技术被称为完美转发,它允许定义一个适用于定义多个函数的模板:带有引用参数、带有常量引用参数和带有右值引用参数的模板。 这是模板定义:
template<class T> block add(T&& y) { return a.add(static_cast<T&&>(y)); }
但是我们还必须为 const
限定符编写相同的代码:
template<class T> block add(T&& y) const { return a.add(static_cast<T&&>(y)); }
我们可以编写以下宏:
//an auxiliary macro #define memb_alias_impl1(_a,_memb,_cv)\ template <class T>\ block _memb(T&& y) _cv\ {\ return _a._memb(static_cast<T&&>(y));\ } //a member alias for a single-parameter member function #define memb_alias(_a,_memb)\ memb_alias_impl1(_a,_memb,)\ memb_alias_impl1(_a,_memb,const)
这一切看起来都不错,除了仍然返回 block
类型。 我们希望此类型派生自 _a._memb
。 为了做到这一点,让我们定义以下类
template<class _R, class _T, class _Arg> struct memb_types { typedef _R return_type; typedef _T class_type; memb_types(_R (_T::*f)(_Arg)) {} };
如果将正确的参数(成员函数)提供给构造函数,则该类将以正确的类型创建。
template<class _R, class _T, class _Arg> memb_types<_R, _T, _Arg> get_memb_types(_R (_T::*f)(_Arg)) { return memb_types<_R, _T, _Arg>(f); }
我们可以对 const
限定符做同样的事情:
template<class _R, class _T, class _Arg> struct memb_types_const { typedef _R return_type; typedef _T class_type; memb_types_const(_R (_T::*f)(_Arg) const) {} }; template<class _R, class _T, class _Arg> memb_types_const<_R, _T, _Arg> get_memb_types(_R (_T::*f)(_Arg) const) { return memb_types_const<_R, _T, _Arg>(f); }
现在我们可以正确定义 memb_alias_impl1
和 memb_alias
#define memb_alias_impl1(_a,_memb,_cv)\ template <class Args>\ typename decltype(get_memb_types(&decltype(_a)::_memb))::return_type _memb(Args&& y) _cv\ {\ return _a._memb(std::forward<Args>(y) );\ } #define memb_alias(_a,_memb)\ memb_alias_impl1(_a,_memb,)\ memb_alias_impl1(_a,_memb,const)
然后,对于没有参数的函数(我们的程序中只有一个),也应该这样做:我们将创建 memb_alias0
。 现在我们可以将类 B
重写如下:
class B { A a; public: // constructors B(const block& x1):a(x1) {} B(block&& x1):a(std::move(x1)) {} // methods memb_alias (a,add); memb_alias0 (a,get); };
方法的定义更短,也更不容易出错。 想象一下,您必须为多个类编写更多这样的别名。
问题在于我们必须为我们想要使用的每个参数数量编写一个单独的成员函数别名:memb_alias2
,memb_alias3
等。 这不太方便,尽管是可能的。 一个更好的技术是为所有成员函数提供一个 memb_alias
。 为了做到这一点,我们必须使用可变参数
模板。
高级技术:使用可变参数模板
使用可变参数模板,我们可以立即处理所有数量的参数。 memb_types
结构和 get_member_types
函数将定义如下:
template<class _R, class _T, class ... _Arg> struct memb_types { typedef _R return_type; typedef _T class_type; memb_types(_R (_T::*f)(_Arg...)) {} }; template<class _R, class _T, class ..._Arg> memb_types<_R, _T, _Arg...> get_memb_types(_R (_T::*f)(_Arg...)) { return memb_types<_R, _T, _Arg...>(f); }
显然,我们还必须为 const
成员函数定义相同的构造。 之后,memb_alias_impl
可以定义如下:
#define memb_alias_impl(_a,_memb,_cv)\ template <class ... Args>\ decltype(get_memb_types(&decltype(_a)::_memb))::return_type _memb(Args&& ... y) _cv\ {\ return _a._memb(std::forward<Args>(y) ... );\ }
现在 memb_alias
的定义看起来很简单:
#define memb_alias(_a,_memb)\ memb_alias_impl(_a,_memb,)\ memb_alias_impl(_a,_memb,const)
现在,类 B
中的方法可以重写如下
memb_alias (a,add); memb_alias (a,get);
您可能需要检查该程序是否将打印完全相同的内容。