可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have the following C++-class:
// Header-File
class A
{
public:
A();
private:
B m_B;
C m_C;
};
// cpp-File
A::A()
: m_B(1)
{
m_B.doSomething();
m_B.doMore();
m_C = C(m_B.getSomeValue());
}
I now would like to avoid the class A
to call any constructor of C m_C
. Because on the last line in A::A()
, I'm anyways going to initialize m_C
myself because I need to prepare m_B
first. I could provide an empty default constructor for class B
. But that's not the idea.
I have already tried to add m_C(NULL)
to the init-list of A::A()
. Sometimes it worked, sometimes it said there was no constructor taking NULL
as an argument.
So how can I have m_C
left uninitialized? I know that with pointers, the m_C(NULL)
-way works. And I don't want to allocate it dynamically using new
.
Any idea is appreciated.
回答1:
What you ask is forbidden - and correctly so. This ensures that every member is correctly initialized. Do not try to work around it - try to structure your classes that they work with it.
Idea:
- C has a constructor that does nothing
- C has an initialization method that makes the class usable
- C tracks whether it has been initialized correctly or not and returns appropriate errors if used without initialization.
回答2:
I don't see a good way to achieve what you want. This must be a workaround:
// Header-File
class A
{
public:
A();
private:
B m_B;
C m_C;
static int prepareC(B& b);
};
// cpp-File
A::A()
: m_B(1)
, m_C(prepareC(m_B))
{
}
int A::prepareC(B& b)
{
b.doSomething();
b.doMore();
return b.getSomeValue();
}
Please ensure that m_B.doSomething()
, m_B.doMore()
and m_B.getSomeValue()
don't touch m_C
(directly or indirectly).
As @Tobias correctly mentions, this solution depends on the order of initialization. You need to ensure that the definitions of m_B
and m_C
are in this order.
Updated the code according to @Loki's idea.
回答3:
Tricky, but can be done.
What you need is a way to adding behavior to the member variable. So the variable is initialized or maybe not. Let's call it "Maybe"
If you do it in a generic way, you want a template class to encapsulate that behavior and apply it to any type:
template<class T>
Maybe {
public:
Maybe() : m_has(false) {}
// If we want to start with the value, call the constructor
Maybe(const T& v) : m_has(true) { new (m_value) T(v); }
// If we have some value, make sure to call the destructor
˜Maybe() { if (m_has) reinterpret_cast<T*>(m_value)->˜T(); }
// Add the value latter on
void setValue(const T& v) {
if (m_has) {
reinterpret_cast<T>(*m_value) = v;
} else {
m_has = true;
new (m_value) T(v);
}
}
bool hasValue() const { return m_has; }
const T& value() const { return reinterpret_cast<T&>(*m_value); }
T& value() { return reinterpret_cast<T&>(m_value); }
private:
bool m_has;
// Reserve the memory for the object, but dont initialize it - dont call it T
uint8_t m_value[sizeof(T)];
};
I wrote the code out the top of my head, so there might be some typos or small details to be adjusted. I know it works though.
Now, just call your member as Maybe and then you don't have to create the empty constructor.
回答4:
How about using technique described in this QA?
Prevent calls to default constructor for an array inside class
std::aligned_storage<sizeof(T[n]), alignof(T)>::type
Or, you also can consider using of union
. AFAIK, unions will be initialized only with first named member's constructor.
For example,
union
{
uint8_t _nothing = 0;
C c;
};
According to the standard mentioned in the QA, c
will be zero-initialized, and its constructor will not be called.
回答5:
You can't.
All member variables are full constructed when the construcotr code block is entered. This means there constructors must be called.
But you can work around this restriction.
// Header-File
class A
{
struct Initer
{
Initer(B& b)
: m_b(b)
{
m_b.doSomething();
m_b.doMore();
}
operator int() // assuming getSomeValue() returns int.
{
return m_b.getSomeValue();
}
B& m_b;
};
public:
A();
private: // order important.
B m_B;
C m_C;
};
// cpp-File
A::A()
: m_B(1)
, m_C(Initer(m_B))
{
}
回答6:
The pointer sounds like the only clean solution to me. The only other solution I see is to have a default constructor for C that does nothing and have an initialising method in C you call yourself later.
m_C.Initialise( m_B.getSomeValue() );
回答7:
Easiest is storing pointers to a B
and a C
. These can be initialized to 0, omitting any construction. Be careful not to dereference a null pointer and delete it in the destructor of A
(or use std::unique_ptr
/boost::scoped_ptr
).
But why not initialize m_B
first (through a proper constructor call, not in A::A()
, and then use that initialized B
instance to initialize m_C
? It will call for a small rewrite, but I bet it'll be worth the code cleanup.
回答8:
If you don't want to allocate it dynamically using new
for code clutter/exception safety reasons, you can use a std::unique_ptr
or std::auto_ptr
to solve this problem.
A solution that avoids new
is to edit C
to have a two-step initialization process. The constructor would then construct a "zombie" object, and you'd have to call an Initialize
method on that m_C
instance to finish your initialization. This is similar to the existing cases you found where you could pass NULL
to the constructor, and later go back to initialize the object.
Edit:
I thought of this earlier (even though it looks much like other people's solutions). But I had to get some confirmation that this wouldn't break before I added this solution - C++ can be quite tricky, and I don't use it very often :)
This is cleaner than my other suggestions, and doesn't require you to mess with any implementation but that of A
.
Simply use a static method as the middle-man on your initialization:
class A
{
public:
A();
private:
static int InitFromB(B& b)
{
b.doSomething();
b.doMore();
return b.getSomeValue();
}
// m_B must be initialized before m_C
B m_B;
C m_C;
};
A::A()
: m_B(1)
, m_C(InitFromB(m_B))
{
}
Note that this means you can't allow m_B
to depend on the instance of A
or C
at all, whereas the solutions at the top of this answer might allow you to pass A
or m_C
into m_B
's methods.
回答9:
Just use comma expressions:
A::A()
: m_B(1)
, m_c(m_B.doSomething(), m_B.doMore(), m_B.getSomeValue())
{
}
Obviously, as others have explained, m_B
better be declared before m_C
else m_B.doSomething()
invokes undefined behavior.
回答10:
Here we have the building blocks:
#include <iostream>
class C
{
public:
C(int i){std::cout << "C::C(" << i << ")" << std::endl;}
};
class B
{
public:
B(int i){std::cout << "B::B(" << i << ")" << std::endl;}
void doSomething(){std::cout << "B::doSomething()" << std::endl;}
void doMore(){std::cout << "B::doMore()" << std::endl;}
int getSomeValue(){return 42;}
};
If you want to make a new kind of construction for B consider making a derived class:
class B1 : public B
{
public:
B1() : B(1)
{
doSomething();
doMore();
}
};
Now use the class B1 that is derived from B:
class A
{
private:
B1 _b;
C _c;
public:
A() : _c(_b.getSomeValue()){std::cout << "A::A()" << std::endl;}
};
And then:
int main()
{
A a;
}
Output:
B::B(1)
B::doSomething()
B::doMore()
C::C(42)
A::A()