简体   繁体   中英

c++ templatizing class constructor

I have a class that has a constructor taking quite a few parameters

enum class FooType {FOO_A, FOO_B, FOO_C};

class Foo {
    Foo(const double a, const double b, .... const double n);
}

depending on the 'type', I only need a certain subset of the params. At the moment there are various constructors with different number of inputs, but some new types will be added so that the number of inputs is the same. I could just add the type to the constructor, have a long switch in it, but the params list is quite long.

Foo(FooType type, const double a, const double b, .... const double n) {
    if (type = FooType::FOO_A) {
        ...
    } else if ....

}

Doesn't seem too bad, but I also don't like having that long parameter list. Seems to easy to make typos that are a pain to debug. So I can a.) pass a structure in b.) do something else

and I am just curious about potential b solutions.

Is it possible to templateize this such that I could create a template constructor and call the constructor with something like

std::make_shared<Foo<FooType::FOO_A>>(a, b, c);

Note: I don't want to use inheritance since the rest of the class' functionality has absolutely no use/need for it.

This could be a use case for the named parameters idiom: http://www.cs.technion.ac.il/users/yechiel/c++-faq/named-parameter-idiom.html .

That would allow your constructor call to look like this:

File f = OpenFile("foo.txt")
           .readonly()
           .createIfNotExist()
           .appendWhenWriting()
           .blockSize(1024)
           .unbuffered()
           .exclusiveAccess();

Instead of the above example, you could have a helper class that contains all the named parameters and your class constructor would take an instance of the parameters class as its parameters.

This lets you freely pick the set of parameters that you initialize at construction time. If you want to enforce different subsets being initialized for the different types, then you should just write different constructor versions.

Here is how you could make a templated constructor using a builder pattern:

class Foo {
    double a;
    int b;
    double c;
public:
    Foo(double a, int b, char c) {

    }
};

template <FooType Type>
class Builder { };

template <>
class Builder<FooType::FOO_A> {
    double _a;
public:
    Builder& a(double val) { _a = val; return *this; }
    Foo build() { return { _a, 0, 0 }; }
};

template <>
class Builder<FooType::FOO_B> {
    int _b;
public:
    Builder& b(int val) { _b = val; return *this; }
    Foo build() { return { 0.0, _b, 0 }; }
};

template <>
class Builder<FooType::FOO_C> {
    char _c;
public:
    Builder& c(char val) { _c = val; return *this; }
    Foo build() { return { 0.0, 0, _c }; }
};

The Builder class is templated as you wanted and the code you were executing in that if else statement, you can execute in builder's constructor or the build function on the instance of Foo you will be returning.

In the example a is relevant for FOO_A , b is relevant for FOO_B and c for FOO_C and other values get initialized to their default value.

This is how you would use it:

int main() {
    Foo testA = Builder<FooType::FOO_A>().a(12.5).build();
    Foo testB = Builder<FooType::FOO_B>().b(10).build();
    Foo testC = Builder<FooType::FOO_C>().c('x').build();
    return 0;
}

It is a pretty small example for a builder pattern, but from your example it seems like you are using much more arguments. To add another argument to any of the builder specializations in form Builder& typeName(Type val) { _typeName = val; return *this; } Builder& typeName(Type val) { _typeName = val; return *this; } Builder& typeName(Type val) { _typeName = val; return *this; } (it should return self reference so these funtions can be chained).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM