具有抽象基类的工厂模式 - 按ref或按值返回?范围和切片的问题

时间:2014-04-02 10:41:22

标签: c++ pass-by-reference factory-pattern object-slicing

我有一个类似于下面的代码示例的类型层次结构,我正在尝试通过工厂模式实例化它们(或者,是迂腐的,而不是构建器模式,因为我的工厂从XML文档中获取输入。但我离题了。

但是我尝试这样做,我遇到的问题我怀疑是由于切片,如果我按值返回,或者是作为范围,如果我通过引用返回。

以下程序,例如a.doA()C::doStuff()行上的段错误。如果我将呼叫更改为value_C_factory<C>()而不是ref_C_factory<C>(),我会收到一些警告,说明“返回临时引用”的效果,但程序编译,而段错误改为b.doB() on下一行(没有打印a.doA() ...)的任何内容。

来自gdb的回溯看起来像这样 - 第二行是我上面提到的代码中的一行

#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

导致这些段错误的原因是什么?正如我怀疑的那样,是否在值/参考案例中切片/确定范围?如果没有,那是什么?最重要的是,从输入数据构建我的实例的好方法是什么?

代码示例

下面的代码应该编译并给出上述行为,例如: GCC 4.8,使用
gcc -g -Wall -std=c++11 -o test test.cpp(无论如何,这就是我所做的)。

#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();
}

2 个答案:

答案 0 :(得分:2)

您有两个问题,都是由未定义的行为引起的。

首先,您无法返回对局部变量的引用。一旦函数返回并且局部变量超出范围并被破坏,返回的引用会引用什么?

另一个问题是您存储对临时值的引用。当您创建CTC(value_A_factory<A>(), value_B_factory<B>())时,value_X_factory函数返回的值为 temporary ,并且在完整表达式({{1}完成了。

答案 1 :(得分:2)

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

返回对局部变量的引用是未定义的行为。

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

value_A_factory返回的值的生命周期将是表达式TC(...)的结尾。之后,C中的引用正在悬空。

如果你真的想将接口和工厂用于多态类型,那么动态内存分配和某种所有权方案就没有其他替代方案。最简单的是C简单地假设其成员的所有权并负责删除它们。

#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;
}