1

I have a type hierarchy similar to the code sample below, and I'm trying to instantiate them via the factory pattern (or, to be pedantic, rather the builder pattern, as my factory takes input from an XML document... but I digress).

However I try to do this, I'm running into problems which I suspect are due to either slicing, if I return by value, or to scoping, if I return by reference.

The below program, for instance, segfaults on the line a.doA() inside C::doStuff(). If I change the call to value_C_factory<C>() to ref_C_factory<C>() instead, I get a couple of warnings to the effect of "returning reference to temporary", but the program compiles, segfaults instead on b.doB() on the next line (without having printed anything from a.doA()...).

The backtrace from gdb looks like this - the second line is the one in my code referred to above

#0  0x00007ffff7dbddb0 in vtable for std::ctype<char> () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#1  0x00000000004010e9 in C::doStuff (this=0x7fffffffdd00) at syntax.cpp:57
#2  0x0000000000400cf2 in main () at syntax.cpp:95

What is causing these segfaults? Is it, as I suspect, slicing/scoping in the value/reference case? If not, what is it? And most importantly, what is a good way to build my instances from the input data?

Code sample

The code below should compile and give the above behavior with e.g. GCC 4.8, using
gcc -g -Wall -std=c++11 -o test test.cpp (that's what I do, anyway).

#include <iostream>
#include <typeinfo>

class IA {
public:
    virtual void doA() const = 0;
    virtual ~IA() { }
};

class A : public IA {
private:
    std::string atask;
public:
    explicit A(const std::string &task) : atask(task) {
        std::cout << "Created A with task " << atask << std::endl;
    }

    void doA() const {
        std::cout << "I did A! " << atask << std::endl;
    }
};

class IB {
public:
    virtual void doB() const = 0;
    virtual ~IB() { }
};

class B : public IB {
private:
    std::string btask;
public:
    explicit B(const std::string &task) : btask(task) {
        std::cout << "Created B with task " << btask << std::endl;
    }

    void doB() const {
        std::cout << "I did B! " << btask << std::endl;
    }
};

class IC {
public:
    void doStuff() const;
    virtual ~IC() { }
};

class C : public IC {
private:
    const IA &a;
    const IB &b;

public:
    C(const IA &a, const IB &b) : a(a), b(b) { }

    void doStuff() const {
        a.doA();    // with value factory method, segfault here
        b.doB();    // with reference factory, segfault here instead
    }
};

template<typename TA>
TA value_A_factory() {
    return TA("a value");
}

template<typename TB>
TB value_B_factory() {
    return TB("b value");
}

template<typename TC>
TC value_C_factory() {
    return TC(value_A_factory<A>(), value_B_factory<B>());
}


template<typename TA>
const TA &ref_A_factory() {
    return TA("a ref");
}

template<typename TB>
const TB &ref_B_factory() {
    return TB("b ref");
}

template<typename TC>
const TC &ref_C_factory() {
    const TC &c(ref_A_factory<A>(), ref_B_factory<B>());
    return c;
}

int main() {
    C c = value_C_factory<C>();
    std::cout << typeid(c).name() << std::endl;
    c.doStuff();
}
Tomas Aschan
  • 58,548
  • 56
  • 243
  • 402
  • @JoachimPileborg: I was under the impression that if I returned a `const ref` it would actually be OK. But I admit that I'm quite confused here, so you are most likely more correct than I am. However, if I don't by reference, won't the argument be sliced when passed to the `C` constructor? – Tomas Aschan Apr 02 '14 at 10:47
  • You are correct that there would be slicing, but you are wrong about how a `const&` extends the lifetime of a temporary. It is until the end of the expression it occurs in (in your case the constructor). After that expression the temporaries will be destroyed and your references in `C` are dangling. – pmr Apr 02 '14 at 10:49

2 Answers2

2

You have two problems, both caused by undefined behavior.

The first is that you can't return a reference to a local variable. Once the function returns and the local variable goes out of scope and is destructed, what does the returned reference then reference?

The other problem is that you store references to temporary values. When you create your C class like TC(value_A_factory<A>(), value_B_factory<B>()) the values returned by the value_X_factory functions are temporary, and will be destroyed once the complete expression (TC(...)) is done.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • OK. So the reference approach is probably stillborn, then. Is there away to avoid the slicing problems when returning by value? – Tomas Aschan Apr 02 '14 at 10:54
  • This is not entirely correct. Returning a const reference is different from a non-const reference. If I remember correctly, this could be used in C++03 to avoid copying class instances. – Danvil Apr 02 '14 at 10:56
  • @TomasLycken Pointers? You might want to read about [`std::unique_ptr`](http://en.cppreference.com/w/cpp/memory/unique_ptr). – Some programmer dude Apr 02 '14 at 10:57
  • 2
    @Danvil Constant references can extend the lifetime, but not past the end of the expression the reference is involved in. – Some programmer dude Apr 02 '14 at 10:59
  • @JoachimPileborg: Yes, he can hand it through by const reference (even a temporary) but at some point he hast to store it. – Danvil Apr 02 '14 at 11:02
2

In

template<typename TA>
const TA &ref_A_factory() {
    return TA("a ref");
}

returning a reference to a local variable is undefined behavior.

In

TC(value_A_factory<A>(), ...)

the lifetime of the value returned by value_A_factory will be the end of the expression TC(...). After that your references in C are dangling.

If you really want to use interfaces and factories for polymorphic types, there is no real alternative to dynamic memory allocation and some kind of ownership scheme. The simplest would be for C to simply assume ownership of its members and take care of their deletion.

#include <memory>
#include <cassert>

struct IA { 
  virtual void doA() const = 0;
  virtual ~IA() { }; 
};

struct A : IA { 
  void doA() const override {}
};

struct C {
  /* C assumes ownership of a. a cannot be null. */
  C(IA* a) : a{a} { assert(a && "C(IA* a): a was null"); };
private:
  std::unique_ptr<IA> a;
};

C factory() {
  return C{new A};
}

int main()
{
  C c = factory();
  return 0;
}
Danvil
  • 22,240
  • 19
  • 65
  • 88
pmr
  • 58,701
  • 10
  • 113
  • 156
  • "Returning a reference to a local variable is undefined behavior." this is not true for const references! – Danvil Apr 02 '14 at 11:01
  • 2
    @Danvil You are wrong. Even returning a const reference to a local variable is undefined behavior. There is even a god damn warning for it. http://ideone.com/8tR6Q7 – pmr Apr 02 '14 at 11:11
  • 1
    @Danvil And this http://stackoverflow.com/questions/1465851/returning-const-reference-to-local-variable-from-a-function – pmr Apr 02 '14 at 11:13
  • I see my mistake now. The standard says "A temporary bound to the returned value in a function return statement (6.6.3) persists until the function exits.". I thought in the case of a const reference it is kept alive until the return value is handled in the "outer" expression. – Danvil Apr 02 '14 at 12:16
  • @Danvil Is there still something wrong with this answer or why do you keep the downvote? – pmr Apr 02 '14 at 12:19
  • I can not undo it because 1 hour has passed :( – Danvil Apr 02 '14 at 12:20
  • Fixed a very minor typo - now I can upvote :) (this is somehow silly...) – Danvil Apr 02 '14 at 12:25
  • OK. I was hoping to avoid having to do this with pointers. You say "if you really want to use interfaces and factories for polymorphic types" - what other options are there? Is this question really an [XY-problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem)? – Tomas Aschan Apr 02 '14 at 13:07
  • @TomasLycken You probably can design around them in some cases, but often enough they are the straight forward solution. If you are confident in your design use the solution I have proposed. – pmr Apr 02 '14 at 13:14