【问题标题】:Copy-constructing data members of abstract type复制构造抽象类型的数据成员
【发布时间】:2020-03-01 15:13:36
【问题描述】:

考虑以下场景:

class AbsBase {
public:
    virtual double foo() = 0;
};

class Concrete1 : public AbsBase {
public:
    double foo() {
        return 1;
    }
};

class Concrete2 : public AbsBase {
public:
    double foo() {
        return 2;
    }
};

struct Config {
    /**
     * Must use a pointer to `AbsBase`, since it is an abstract base class, 
     * and the concrete instances could be of either of the derived classes.
     */
    AbsBase* thing;

    void setThing(AbsBase* t) {
        thing = t;
    }
};

工作,但如果thing 是传递给setThing 的参数的副本,我会更喜欢它。 IE。如果thingConcrete1 类型(而不是抽象基类),我可以这样做:

Concrete1 thing;
void setThing(const Concrete1& t) {
    thing = Concrete1(t);
}

但是,如果不实例化抽象类型的对象(这是非法的),我看不到实现此目的的方法。有什么想法吗?

【问题讨论】:

  • 您可以尝试通过声明 thing 来使其成为引用:AbsBase &thing
  • 否则我只是看不出你想要什么是可能的,因为它甚至没有意义。
  • 但是通过引用传递不会复制对象
  • @marvinIsSacul 这将消除更改它所指对象的可能性。
  • 但是这样想,你怎么能创建一个不存在的东西的副本(抽象类型)?

标签: c++ abstract-class base-class


【解决方案1】:

您可以使用模板函数来实现所需的行为。 我建议也使用std::unique_ptr 来防止内存问题。

class AbsBase {
public:
    virtual ~AbsBase() = default;
    virtual double foo() = 0;
};

class Concrete1 : public AbsBase {
public:
    double foo() {
        return 1;
    }
};

class Concrete2 : public AbsBase {
public:
    double foo() {
        return 2;
    }
};

struct Config {
    /**
     * Must use a pointer to `AbsBase`, since it is an abstract base class, 
     * and the concrete instances could be of either of the derived classes.
     */
    std::unique_ptr<AbsBase> thing;

    template <typename T>
    void setThing(const T& t) {
        static_assert(std::is_base_of<AbsBase, T>::value, "T must inherit AbsBase");
        static_assert(std::is_copy_constructible<T>::value, "T must be copy-constructible");
        thing.reset(new T{t});
    }

    void test()
    {
        std::cout << (thing?thing->foo():0.0) << std::endl;
    }
};

https://gcc.godbolt.org/z/fID5UM

【讨论】:

  • 这看起来是一个很好的解决方案。请问new T{t}new T(t)的区别?
  • 在这种情况下没有。如果您想搜索更多信息,则称为统一初始化。
  • 制作AbsBase的虚拟析构函数以避免内存泄漏。因为调用thing.reset()时不会调用Concrete2的Concrete1的析构函数
  • @SergeyAleksandrovich 好点。在这种情况下应该不会受到伤害,但通常是个好主意。
【解决方案2】:

这是一个示例(使用std::cout 以获得更多理解)。不要忘记创建AbsBase 的虚拟析构函数以避免内存泄漏。

#include <iostream>

class AbsBase {
public:
    virtual double foo() = 0;
    virtual ~AbsBase() {
        std::cout << "destructor AbsBase" << std::endl;
    }
};

class Concrete1 : public AbsBase {
public:
    Concrete1() {
        std::cout << "default constructor of Concrete1" << std::endl;
    }
    Concrete1(const Concrete1 & other) {
        std::cout << "copy constructor of Concrete1" << std::endl;
    }
    Concrete1 & operator=(const Concrete1 & other) {
        std::cout << "assignment operator of Concrete1" << std::endl;
    }
    virtual ~Concrete1() {
        std::cout << "destructor of Concrete1" << std::endl;
    }
    double foo() {
        return 1;
    }
};

class Concrete2 : public AbsBase {
public:
    Concrete2() {
        std::cout << "default constructor of Concrete2" << std::endl;
    }
    Concrete2(const Concrete2 & other) {
        std::cout << "copy constructor of Concrete2" << std::endl;
    }
    Concrete2 & operator=(const Concrete2 & other) {
        std::cout << "assignment operator of Concrete2" << std::endl;
    }
    virtual ~Concrete2() {
        std::cout << "destructor of Concrete2" << std::endl;
    }
    double foo() {
        return 2;
    }
};

class Config {
private:
    /**
     * Must use a pointer to `AbsBase`, since it is an abstract base class, 
     * and the concrete instances could be of either of the derived classes.
     */
    AbsBase* thing;

public:
    Config() : thing(0) {
        std::cout << "constructor of Config" << std::endl;
    }
    ~Config() {
        std::cout << "destructor of Config" << std::endl;
        if (thing) {
            std::cout << "delete thing" << std::endl;
            delete thing;
        }
    }
    template<typename T>
    void setThing(T & t) {
        std::cout << "setThing" << std::endl;
        if (thing) {
            std::cout << "delete thing" << std::endl;
            delete thing;
        }
        thing = new T (t);
    }
    AbsBase* getThing() {
        return thing;
    }
};

int main() {
    Config config;
    Concrete1 concrete1;
    Concrete2 concrete2;
    std::cout << "=================" << std::endl;
    std::cout << "save concrete1" << std::endl;
    config.setThing(concrete1);
    std::cout << config.getThing()-> foo() << std::endl;
    std::cout << "=================" << std::endl;
    std::cout << "save concrete2" << std::endl;
    config.setThing(concrete2);
    std::cout << config.getThing()-> foo() << std::endl;
    std::cout << "=================" << std::endl;
    std::cout << "destruction of all local variables" << std::endl;
    return 0;
}

输出:

constructor of Config
default constructor of Concrete1
default constructor of Concrete2
=================
save concrete1
setThing
copy constructor of Concrete1
1
=================
save concrete2
setThing
delete thing
destructor of Concrete1
destructor AbsBase
copy constructor of Concrete2
2
=================
destruction of all local variables
destructor of Concrete2
destructor AbsBase
destructor of Concrete1
destructor AbsBase
destructor of Config
delete thing
destructor of Concrete2
destructor AbsBase

【讨论】:

    【解决方案3】:

    除了使用clone 方法的duplicate 之外,在您的情况下,您还可以通过模板接受具体类型。

    struct Config {
        AbsBase* thing;
        template <typename T>
        void setThing(T& t) {
            thing = new T(t);
        }
    };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-08-18
      • 2011-07-21
      • 2010-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多