在C ++中以通用方式构造对象,设置字段和返回对象

时间:2017-09-27 21:54:25

标签: c++ templates

我的代码库中有以下模式近千次。

Foo *foo = new Foo();
foo->greeting = "Hello";
foo->legs = 4;

Bar *bar = new Bar();
bar->firstName = "John";
bar->lastName = "Smith";
bar->height = 5.6;

我想使用如下语法来减少这个问题。

Foo *foo = construct<Foo, greeting, legs>("Hello", 4);
Bar *bar = construct<Bar, firstName, lastName, height>("John", "Smith", 5.6);

我想避免使用实际的C ++构造函数,因为为每个类编写Foo(std::string greeting, int legs) : greeting(greeting), legs(legs) {}需要付出很多努力,是多余的,并且当类层次结构非常高并且需要调用超类时,它是一团糟。 我想这可能是模板函数或宏,但我无法弄明白。什么是最接近的?

我应该指出C#内置了这个独特的功能。

Foo *foo = new Foo{greeting = "Hello", legs = 4};

意图

以下是我的类层次结构的一小部分示例。实际上它包含了30多个类,其中包含更多的字段和方法。我使用构造函数,但我从不使用带有参数的构造函数,因为预期调用者使用默认字段构造它并在构造之后设置它们需要的内容。否则构造函数将有50多个参数,调用者必须明确指定所有默认值。

struct Widget {
    Vec pos = Vec(0, 0);
    Vec size = Vec(0, 0);
    float zoom = 1.0;
};

struct FramebufferWidget : Widget {
    float padding = 0.0;
    float oversampling = 1.0;
};

struct SVGWidget : FramebufferWidget {
    std::string filename;
};

struct SVGKnobWidget : SVGWidget {
    float value = 0.0;
    float minValue = 0.0;
    float maxValue = 1.0;
};

struct MomentarySVGKnobWidget : SVGKnobWidget {
    // No fields, just additional behavior in methods
}

...

addChild(construct<SVGKnobWidget, filename, maxValue>("knob.svg", 2.0))

注意眼睛比调用

更简单,更容易
addChild(SVGKnobWidget(Vec(0, 0), Vec(0, 0), 1.0, 0.0, 1.0, "knob.svg", 0.0, 0.0, 2.0));

我需要构造指针,因为在构造之后,我立即将所有权转移到addChild函数。

2 个答案:

答案 0 :(得分:0)

C ++ 11为C ++带来了统一的初始化。即你可以做到

Foo *foo = new Foo{"Hello", 4};

由user4581301建议。但这假设你知道Foo的布局。

您将找不到与C#中相同的语法,因为C ++不支持命名参数。 你可以使用一些聪明的东西看http://vitiy.info/named-tuple-for-cplusplus/IOD或者其他许多尝试来接近某些东西(甚至是接近命名元组的东西),但我发现语法也常常很烦人,加上一些像IOD一样,要求您预先声明命名的参数名称。

- 编辑
我现在记得boost parameters直接实现命名参数(使用与IOD中相同的基本概念)。虽然它需要相当多的锅炉板代码:

myclass y(_index = 12, _name = "sally");

答案 1 :(得分:0)

到目前为止,这是我的尝试。我已将此作为公共维基,因此欢迎改进!

显式宏

#define construct0(_T, _N) _T *_N = new _T()
#define construct1(_T, _N, _F1, _V1) construct0(_T, _N); _N._F1 = _V1
#define construct2(_T, _N, _F1, _V1, _F2, _V2) construct1(_T, _N, _F1, _V1); _N._F2 = _V2
#define construct3(_T, _N, _F1, _V1, _F2, _V2, _F3, _V3) construct2(_T, _N, _F1, _V1, _F2, _V2); _N._F3 = _V3
#define construct4(_T, _N, _F1, _V1, _F2, _V2, _F3, _V3, _F4, _V4) construct3(_T, _N, _F1, _V1, _F2, _V2, _F3, _V3); _N._F4 = _V4

construct3(Bar, bar, firstName, "John", lastName, "Smith", height, 5.6);
// Expands to
Bar *bar = new Bar(); bar.firstName = "John"; bar.lastName = "Smith"; bar.height = 5.6;

优点:简单实施。

缺点:没有返回实例化的值,因此不能使用addChild(constructN(...)),除非您使用GCC语句Expr扩展。 在范围内重用相同的变量名会导致冲突。 仅支持有限数量的参数。

我不认为__VA_ARGS__可以在这里提供帮助。

成员指针模板

template<typename T>
T *construct() {
    return new T();
}

template<typename T, typename F, typename V, typename... Args>
T *construct(F f, V v, Args... args) {
    T *o = construct<T>(args...);
    (o->*f) = v;
    return o;
}

construct<Bar>(&Bar::firstName, "John", &Bar::lastName, "Smith", &Bar::height, 5.6);

优点:返回值,不会使调用者的范围混乱。

缺点:在每个字段名称之前需要重复&Bar::

成员指针模板+显式宏

更进一步,因为我们知道标识符不能用作模板参数,我们可以让宏执行此操作。我们也可以使用GCC ##技巧来计算参数。

#define CONSTRUCT_ARGS1(T, f, v) &T::f, v
#define CONSTRUCT_ARGS2(T, f, v, ...) &T::f, v, CONSTRUCT_ARGS1(T, __VA_ARGS__)
#define CONSTRUCT_ARGS3(T, f, v, ...) &T::f, v, CONSTRUCT_ARGS2(T, __VA_ARGS__)
#define CONSTRUCT_ARGS4(T, f, v, ...) &T::f, v, CONSTRUCT_ARGS3(T, __VA_ARGS__)
#define CONSTRUCT_ARGS5(T, f, v, ...) &T::f, v, CONSTRUCT_ARGS4(T, __VA_ARGS__)
#define CONSTRUCT_ARGS6(T, f, v, ...) &T::f, v, CONSTRUCT_ARGS5(T, __VA_ARGS__)
#define CONSTRUCT_ARGS7(T, f, v, ...) &T::f, v, CONSTRUCT_ARGS6(T, __VA_ARGS__)
#define CONSTRUCT_ARGS8(T, f, v, ...) &T::f, v, CONSTRUCT_ARGS7(T, __VA_ARGS__)
#define CONSTRUCT_ARGS9(T, f, v, ...) &T::f, v, CONSTRUCT_ARGS8(T, __VA_ARGS__)
#define ARG20(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18_, _19, _arg, ...) _arg
#define CONSTRUCT_COUNT(...) ARG20(__VA_ARGS__, 10, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1)
#define CONSTRUCT_JOIN(count, ...) CONSTRUCT_ARGS ## count(__VA_ARGS__)
#define CONSTRUCT_MACRO(count, ...) CONSTRUCT_JOIN(count, __VA_ARGS__)
#define CONSTRUCT_ARGS(T, ...) CONSTRUCT_MACRO(CONSTRUCT_COUNT(__VA_ARGS__), T, __VA_ARGS__)
#define CONSTRUCT(T, ...) construct<T>(CONSTRUCT_ARGS(T, __VA_ARGS__))


Bar *bar = CONSTRUCT(Bar, x, 4, y, 6);

优点:最短但

缺点:复杂的实现,最多只能使用N对参数。