Clean way of optionally including members in C++ ctor-initializer
-
01-07-2021 - |
Pregunta
If I have:
Class MyClass {
public:
MyClass( /* args */ );
private:
someType member0;
someType member1;
// ...
someType memberN;
#if defined(FIRST_COMPILE_CONDITION)
someType condition1Member0;
someType condition1Member1;
// ...
someType condition1MemberN;
#endif
#if defined(SECOND_COMPILE_CONDITION)
someType condition2Member0;
someType condition2Member1;
// ...
someType condition2MemberN;
#endif
};
for some arbitrary number of compile conditions, is there a clean way to do the equivalent of this (especially for possibly multiple compile conditions), without putting some permanent member last?
MyClass::MyClass( /* args */ ) :
member0( someValue ),
member1( someValue ),
// ...
memberN( someValue ),
#if defined(FIRST_COMPILE_CONDITION)
condition1Member0( someValue ),
condition1Member1( someValue ),
// ...
condition1MemberN( someValue ),
#endif
#if defined(SECOND_COMPILE_CONDITION)
condition2Member0( someValue ),
condition2Member1( someValue ),
// ...
condition2MemberN( someValue ),
#endif
// ...
{
}
As the above will not compile under most conditions due to the trailing comma.
Edit to clarify:
It probably should be noted explicitly that someValue
is intended to be a potentially independent, arbitrary value that may or may not depend on constructor arguments in each case used.
Solución
You can put the comma before initializers:
MyClass::MyClass() :
member0( someValue )
,member1( someValue )
// ...
, memberN( someValue )
#if defined(FIRST_COMPILE_CONDITION)
,condition1Member0( someValue )
,condition1Member1( someValue )
// ...
,condition1MemberN( someValue )
#endif
// ...
{
}
Otros consejos
Personally, I try to avoid compiler directives as much as possible (except include guards) because they typically hide problems and reflect design issues.
One design issue we all face is windows/linux/mac specific code. There is little way around that to handle cross platform.
To handle this, I skip the compiler directives and address it at the Makefile/project level (depending on your tools). To do that here, you might:
- Add an init_conditional() method to the class and call it in the constructor
- Add class_init_....cpp files for each condition, each with a different implementation of the init_conditional()
- Have the make file decide which to compile and link in
Now, doing that is some work of its own, but is much cleaner in the code.
Note: This loses the protection of member-wise initialization but is a more general solution to the multiple-conditional-compilation problem.
I always use the following way to write initialisation:
MyClass::MyClass()
: member0 ( some_value0 )
, member1 ( some_value1 )
// etc
{}
it is the logically more correct way, as the comma separates a member from its predecessor, not the the next one.
I suppose that's the reason some people move the trailing comma so that it's in front of the next entry:
member0( someValue ),
member1( someValue )
// ...
, memberN( someValue )
#if defined(FIRST_COMPILE_CONDITION)
, condition1Member0( someValue )
, condition1Member1( someValue )
But that's incredibly ugly.
Just reverse your comma logic:
MyClass::MyClass() :
member0( someValue ),
member1( someValue ),
// ...
memberN( someValue )
#if defined(FIRST_COMPILE_CONDITION)
,condition1Member0( someValue )
,condition1Member1( someValue )
// ...
,condition1MemberN( someValue )
#endif
#if defined(SECOND_COMPILE_CONDITION)
,condition2Member0( someValue )
,condition2Member1( someValue )
// ...
,condition2MemberN( someValue )
#endif
// ...
{
}
Note the removal of the comma after the first memberN
When you are able or allowed to use C++11 it becomes really easy
Class MyClass {
public:
MyClass();
private:
someType member0 = someValue;
someType member1 = someValue;
// ...
someType memberN;
#if defined(FIRST_COMPILE_CONDITION)
someType condition1Member0 = someValue;
someType condition1Member1 = someValue;
// ...
someType condition1MemberN = someValue;
#endif
#if defined(SECOND_COMPILE_CONDITION)
someType condition2Member0 = someValue;
someType condition2Member1 = someValue;
// ...
someType condition2MemberN = someValue;
#endif
};
The real big advantage is, you don't have to care about multiple constructors. However, you may achieve this with other approaches, too.