I am wondering if I am using the good approach in the following :
I want to construct a parent class (class A), this class should own an instance of a given "Foo" class
I want the parent class to own a child class member (class B) and this member should have a reference to the foo member of the parent class.
The code below seems to works, however I am wondering whether I was just "lucky" that the compiler was sympathetic enough.
For clarity, I added comment and my question in the comments below.
Thanks !
struct Foo
{
std::string mValue;
};
class B
{
public:
B(const Foo & foo) : mFoo_External(foo) {}
private:
const Foo & mFoo_External; //this is an external reference to the member
//(coming from A)
};
class A
{
public:
//Here is the big question
//Shall I use :
// A(const Foo & foo) : mFoo(foo), mB(mFoo) {}
// or the declaration below
A(const Foo & foo) : mFoo(foo), mB(foo) {}
private:
//According to my understanding, the declaration
//order here *will* be important
//(and I feel this is ugly)
const Foo mFoo;
B mB;
};
void MyTest()
{
std::auto_ptr<Foo> foo(new Foo());
foo->mValue = "Hello";
A a( *foo);
foo.release();
//At this point (after foo.release()), "a" is still OK
//(i.e A.mB.mFooExternal is not broken, although foo is now invalid)
//
//This is under Visual Studio 2005 :
//was I lucky ? Or is it correct C++ ?
}
No, this is broken. Your mB will hold a reference to whatever you passed to the constructor of the A object, not to mFoo. Instead, you should say:
A(const Foo & foo) : mFoo(foo), mB(mFoo) { }
Note that mB is a copy of the constructor argument, and not a reference, so your MyTest function is fine.
Since you want your B object to hold a reference to the parent's member, you must initialize mB with mFoo not foo.
You are correct that the order of the member variables is important, since it determines the order of the initialization. It might come as a surprise that the order of the initializers in the constructor does not determine the order they are called! See Constructor initialization-list evaluation order.
Related
I'm having some issues understanding the following behavior of GTest.
When I have some members inside the fixture class that are initialized in the initializer list and I use them to initialize another member from the fixture, the following behavior happens.
In the first version the Copy Constructor from B is called and then the Parametrized Constructor of B.
In the second version first the Parametrized constructor and then the Copy Constructor is called.
My understanding is that the behavior should me similar in the first version and in the second version,
however the right behavior is seen only in the second version.
If I want to add some members like in the first version how should I do?
class ConfigEndpoint
{
ConfigEndpoint (const B& f_bar0 = B(),
const B& f_bar1 = B(),
const B& f_bar2 = B() )
};
ConfigEndpoint::ConfigEndpoint (const B& f_bar0 = B(),
const B& f_bar1 = B(),
const B& f_bar2 = B() )
: array {f_bar0, f_bar1, f_bar2} {}
Class B has two types of constructors:
// Default one:
B::B()
: m_space(MEMORY),
m_type(32BIT),
m_isConfigured(false)
{}
// Parametrized one:
B::B(int f_barSpace,
int f_barType)
: m_space(f_barSpace),
m_type(f_barType),
m_isConfigured(true)
{}
**//TestFixture -> version 1.**
class CConfigTest : public ::testing::TestWithParam<std::tuple<driverStartAddress, bool>>
{
public:
CConfigTest() :
bar0(MEMORY, 32BIT),
bar1(MEMORY, 32BIT),
bar2(MEMORY, 32BIT),
configEndpoint(bar0,
bar1,
bar2)
{ }
ConfigEndpoint configEndpoint;
const B bar0;
const B bar1;
const B bar2;
};
**//TestFixture -> version 2.**
class CConfigTest : public ::testing::TestWithParam<std::tuple<driverStartAddress, bool>>
{
public:
CConfigTest()
:
configEndpoint( B(MEMORY, 32BIT),
B(MEMORY, 32BIT),
B(MEMORY, 32BIT) )
{ }
ConfigEndpoint configEndpoint;
};
You haven't described what actually happens in both versions (i.e. what you expect vs. what you observe). At the first glance: you create bar[1|2|3] but you pass mock_bar[1|2|3] in the version no. 1.
On a side note: you have a bug in the class A: if you call it without parameters, you bind the temporary object to the const &A. This is fine on the stack, but not for the classes' members. And what is worse, you don't use it in tests (because you pass proper references there), so it will only fail in production or some integration test. See:
Does a const reference class member prolong the life of a temporary?
I've been working on a project and have quite a few classes, a few of which look like this:
class A
{
// stuff;
};
class B
{
A& test;
public:
B(A& _test) :
test(_test)
{};
void doStuff();
};
class C
{
A foo;
B bar(foo);
void exp()
{
bar.doStuff();
}
};
This ends up breaking in class C when C::foo is not a type name. In my bigger project, where everything is broken up into their separate .cpp and .h files, I don't see that error if I #include"C.h" in B.h, but there is still an error in C.cpp where bar is completely unrecognized by my compiler (Visual Studio 2013). There error persists even if A& is an A* instead (changing the code from references to pointers where necessary, of course). Does anyone have any tips to what is going on here?
This line of code:
B bar(foo);
Attempts to declare a member function named bar which returns a B and takes an argument of type foo. However, in your code, foo is not a type - it's a variable. I'm guessing you meant to initialize bar instead:
B bar{foo}; // non-static data member initializers must be done with {}S
or just write out the constructor:
C()
: bar(foo)
{ }
Additionally, this constructor assigns your reference test to the temporary _test:
B(A _test) :
test(_test)
{ }
You want to take _test by reference:
B(A& _test) : test(_test) { }
I have a class Printer with
class Printer{
struct foo{
int i;
};
foo & f;
};
and when I call the constructor of Printer, I need to initialize f since f is a reference, but what I want is first call the constructor of foo and create a instance of it then assign it to f. The problem I have is if I call
Printer::Printer():f(foo(0)){ }
There is an error saying I cannot use reference to a temporary instance of a structure. Any way around this problem?
Thanks
A reference doesn't make any sense in this situation. Try the following instead:
class Printer{
struct foo{
int i;
foo(int i_) : i(i_) {} // Define a constructor
};
foo f; // Not a reference anymore!
public:
Printer::Printer() : f(0) {} // Initialise f
};
I think you actually don't want a reference to foo but foo itself.
When you call foo(0), it creates a structure and returns it as a 'floating' object. You can't assign a reference to it, because no-one 'holds' it and it will be discarded as soon as the constructor exits.
So, if you would like to keep a reference to it, you would first have to store it in an actual object which would be persistent. In this case, you probably want to just keep the whole struct in the class.
You can make Oli's answer slightly shorter by declaring struct foo and defining f in one step:
class Printer{
struct foo{
int i;
foo(int i_) : i(i_) {}
} f;
public:
Printer() : f(0) {}
};
Suppose I have a class Baz that inherits from classes Foo and Bar, in that order. The constructor for class Bar takes a pointer to a Foo object. What I would like to do is to pass this as the Foo object to the Bar constructor:
Baz () : Foo(), Bar(this) {}
A working example:
#include <iostream>
class Bar;
class Foo {
public:
virtual ~Foo() {}
virtual void parse_bar (Bar&) const = 0;
};
class Bar {
private:
const Foo * parser;
public:
Bar (const Foo * parser_in) : parser(parser_in) {}
virtual ~Bar() {}
void parse_self () { parser->parse_bar (*this); }
};
class Baz : public Foo, public Bar {
public:
Baz () : Foo(), Bar(this) {}
virtual void parse_bar (Bar &) const { std::cout << "Hello World\n"; }
};
int main () {
Baz baz;
baz.parse_self();
}
This happens to work on my computer, with my compilers (tested with a couple of them). However section 9.3.2 of the 2003 standard makes me a bit uneasy that I might just be getting lucky, that using this this way is undefined behavior. Strictly speaking, the initializer list is outside the body of the constructor. Here's the relevant text, emphasis mine:
9.3.2 The this pointer
In the body of a nonstatic member function, the keyword this is a non-lvalue expression whose value is the address of the object for which the function is called.
So is my usage legal and well-defined, or is it undefined behavior?
There are two points that have to be noted in this case.
Firstly, in the constructor initializer list this pointer refers to a non-constructed (or not-fully-constructed) object. It is OK to access such pointer, but the object it refers to can only be used in limited ways. See 12.7 in the language specification.
Secondly, in your specific example what you are actually doing is converting this pointer to Foo * type before attempting any access. This is completely safe since by that moment the Foo subobject is fully constructed. (I assume that, whatever access will follow, if any, will be restricted only to the fully constructed Foo subobject).
The only concern is this case is whether it is legal to convert this to Foo * type, i.e. whether the conversion process itself should succeed. The answer is: yes, in case of ordinary (non-virtual) inheritance such conversion is perfectly legal and safe (again, explicitly allowed in 12.7)
It is fine. The C++ standard actually clarifies the use of this pointers in initializer lists:
12.6.2 Initializing bases and members [class.base.init]
Paragraph 7: Names in the expression-list of a mem-initializer are evaluated in the scope of the constructor for which the mem-initializer is specified. [Example:
class X {
int a;
int b;
int i;
int j;
public:
const int& r;
X(int i): r(a), b(i), i(i), j(this->i) {}
};
initializes X::r to refer to X::a, initializes X::b with the
value of the constructor parameter i, initializes X::i with the
value of the constructor parameter i, and initializes X::j with
the value of X::i; this takes place each time an object of class X
is created. ] [Note: because the mem-initializer are evaluated in
the scope of the constructor, the this pointer can be used in the
expression-list of a mem-initializer to refer to the object being
initialized. ]
The type of the this pointer in the initializer of Baz is in fact of type Baz. Of course, you have to be mindful of the fact that not all of the members may have been initialized. Many, if not all, compilers set at their highest warning levels (which you really should be doing anyway) will warn about you passing a this pointer to the base class.
However, it looks like you're making it more complicated that it needs to be. Why not just put the virtual function parse_bar() in the Bar class, and forget about the Foo class?
#include <iostream>
class Bar
{
public:
Bar() {}
virtual ~Bar() {}
void parse_self () { parse_bar(); }
private: // Template method pattern
virtual void parse_bar() const = 0;
};
class Baz : public Bar
{
public:
Baz () {}
private: // Yes, this does override the private `parse_bar()` member!
virtual void parse_bar() const { std::cout << "Hello World\n"; }
};
int main ()
{
Baz baz;
baz.parse_self();
}
This does essentially the same function but with less code.
class A;
class B {
public:
B(A& a) : a(a) {}
private:
A& a;
};
/* Method 1 */
/* warning C4355: 'this' : used in base member initializer list */
/*
class A {
public:
A() : b(*this) {}
private:
B b;
};
*/
/* Method 2 */
/* But I need to manually perform memory dellocation. */
class A {
public:
A() { b = new B(*this); }
~A() { delete b; }
private:
B* b;
};
int main() {
}
Currently, when I try to initialize the reference in B, I am using Method 1. However, Method 1 will flag me warning which is understandable.
Hence, I have to fall back using Method 2, by using dynamic memory allocation.
Is there any better way I can use, without the need of manual memory allocation/ dellocation (OK. I know smart pointer)?
I prefer Method 1, just that I am not comfortable with the warning.
Note this is a warning (so it is dangerous not illegal).
What the compiler is worried about is that you are passing a pointer for an object that has not been fully initialized. Thus if the pointer is used in the B class constructor you are in undefined behavior.
So if you do use this the only thing you can do is assign the pointer to a member variable (reference or pointer). But note be careful of the assignment to a variable as you may invoke an implicit cast (I am not sure if that is actually a problem but the RTTI is not available until the object is fully formed).
What are you trying to achieve by storing the reference?
Doing this is valid.
However, you must ensure (I mean by yourself, there's no way the compiler can do this) that the this is not used to call virtual functions until the object is fully constructed.
Depending on what you're doing, a method might be to factor out the parts of A that B needs, than have A inherit from the part.
struct bar_base; // interface foo wants
struct foo
{
foo(bar_base& pX) :
mX(pX)
{}
bar_base& mX;
};
struct bar_base
{
/* whatever else */
protected:
bar_base& get_base(void)
{
// getting `this` went here; safe because bar_base is initialized
return *this;
}
};
struct bar : bar_base
{
bar(void) :
// bar_base is already initialized, so:
mX(get_base())
{}
foo mX;
};
Obviously, this depends on what you're doing. This makes sure you never get undefined behavior.
But really, it's just warning. If you promise to never use this in B's constructor, you're fine, and can silence the warning this way:
struct bar;
struct foo
{
foo(bar& pX) :
mX(pX)
{}
bar& mX;
};
struct bar
{
bar(void) :
mX(self())
{}
foo mX;
private:
bar& self(void)
{
// fools the warning
return *this;
}
};
Make sure you know what you're doing, though. (Perhaps it could be re-designed?)
Well, one obvious way to avoid the warning is to make B store a pointer-to-A, then you don't have to initialise it in B's constructor/A's initialiser list, and can wait until the body of A's constructor is executing....
Take this warning seriously. Your this object is not yet fully constructed and passing around is not safe (if you ever accidentally call a function on this you invoke UB). Also, there are other techniques for memory management. Try looking up the STL design of allocators.
You could also use RAII/smart pointers to achieve the same effect.
Or, are you trying to write a garbage collector/memory profiler of sorts?