问题
This is a question on the syntax of C++ initializer lists.
Is it possible to call functions from initializer lists without them being arguments to member object constructors?
Code example listed below is paraphrased (paracoded?) from a similar situation at work.
The Situation
- A member variable takes a pointer to a singleton as constructor argument.
- The member variable is constructed by initializer list in its containing class' constructor.
- The singleton has not been created prior to the containing class being constructed.
The Code
#include <iostream>
#define LOG { std::cout << __PRETTY_FUNCTION__ << std::endl; }
namespace
{
template <class T>
class SingletonService
{
public:
static T* Instance() { LOG; return mpT; }
static void InstallInstance(T* pT) { LOG; mpT = pT; }
static void DeleteInstance() { if (mpT) delete mpT; }
protected:
static T* mpT;
};
template <class T>
T* SingletonService<T>::mpT = NULL;
class OneOfMe
{
public:
OneOfMe() { LOG; };
virtual ~OneOfMe() { };
};
class Container
{
public:
Container(OneOfMe* pObj) { LOG; /* Do something with pObj */ }
virtual ~Container() { }
};
int GenerateNum()
{
return 42;
}
class Baz
{
public:
Baz(int num) : mNum(num) { LOG; }
virtual ~Baz() { }
protected:
int mNum;
};
class Bar
{
public:
Bar() : mBaz(GenerateNum()) { LOG; } // Perfectly OK to call function that is argument to member object's non-default ctor.
virtual ~Bar() { };
protected:
Baz mBaz;
};
class Foo
{
public:
Foo()
: SingletonService<OneOfMe>::InstallInstance(new OneOfMe) // Compile error
, mContainer(SingletonService<OneOfMe>::Instance()) { }
virtual ~Foo() { };
protected:
Container mContainer;
};
}
int main(int argc, char* argv[])
{
LOG;
Bar bar;
SingletonService<OneOfMe>::InstallInstance(new OneOfMe); // This works.
Container container(SingletonService<OneOfMe>::Instance()); // And this works.
SingletonService<OneOfMe>::DeleteInstance();
return 0;
}
The compile error
>g++ main.cpp
main.cpp: In constructor ‘<unnamed>::Foo::Foo()’:
main.cpp:45: error: expected class-name before ‘(’ token
main.cpp:45: error: no matching function for call to
‘<unnamed>::Container::Container()’
main.cpp:37: note: candidates are:
<unnamed>::Container::Container(<unnamed>::OneOfMe*)
main.cpp:35: note:
<unnamed>::Container::Container(const<unnamed>::Container&)
main.cpp:45: error: expected ‘{’ before ‘(’ token
The Question
Is it syntactically possible to call a function from a class constructor's initializer list without being an argument to a member object's non-default constructor?
The question is for academic curiosity. I know at least one other solutions is to instantiate the singleton before creating the containing class.
回答1:
You can utilize the comma operator.
In your example
class Foo
{
public:
Foo()
: mContainer((SingletonService<OneOfMe>::InstallInstance(new OneOfMe),
SingletonService<OneOfMe>::Instance()))
{}
virtual ~Foo();
protected:
Container mContainer;
};
Note the additional parentheses around the two expressions, otherwise these would be interpreted as two instead of one parameter.
Another approach to this particular problem could be to return the singleton from InstallInstance()
as well, e.g.
template <class T>
class SingletonService {
public:
static T *InstallInstance(T *pT) { LOG; return mpT = pT; }
};
and then
class Foo {
public:
Foo()
: mContainer(SingletonService<OneOfMe>::InstallInstance(new OneOfMe)) {}
virtual ~Foo();
protected:
Container mContainer;
};
回答2:
Is it possible to call functions from initializer lists without them being arguments to member object constructors?
Something like this maybe works as intended:
void f() {}
struct S {
S(): i{(f(), 0)} {}
int i;
};
int main() {
S s;
}
The basic idea is to rely on the comma operator. In this case, the value returned by the function (if any) is discarded and not used to initialize a member.
Of course, we still exploit the fact that a data member exists, so maybe it is not exactly what you are looking for.
If you want to get rid completely of data members, you can do something similar with a delegated constructor like in the following example:
void f() {}
class S {
S(int) {}
public:
S(): S{(f(), 0)} {}
};
int main() {
S s{};
}
No matters what's the return type of the invoked function. By means of the comma operator, the int
value is used as a tag to dispatch the call to the right compiler and then it is discarded.
来源:https://stackoverflow.com/questions/40389492/c-initializer-list-capabilities-call-functions-without-initializing-member