类包含指向基类的指针,但我不想要它。 (C ++)

时间:2016-06-18 13:14:36

标签: c++ oop pointers polymorphism

一个奇怪的问题标题,但我不知道如何紧凑地描述这种情况。

最近我一直在询问有关制作音频合成软件的问题。这个问题涉及一个特定的问题"我有。我的代码编译并正常工作;它只是一个奇怪的"如果你愿意的话。

我有一个包含指向基类的指针的类。我将详细介绍一个最小的示例,以更详细地显示正在发生的事情。

class SynthNoteBase
{

    // example content
    virtual
    double Sample() = 0;

}; // just for base class pointer

class SynthNoteA : public SynthNoteBase
{

};

class SynthNoteB : public SynthNoteBase
{

}; // define two different types of note -
   // these will synthesize different sounds

每个班级SynthNote*都有一个成员,例如Play()Sample(),它会将样本返回到"播放" /"录制在音频中文件" /等

现在,我有一个Synthesizer对象(类),其中包含许多SynthesizerKey个对象。每个SynthesizerKey类都包含指向基类SynthNoteBase的指针。

说明:" Synthesizer"有许多不同的Key s,就像真正的合成器一样。这些Key对象将采用Play()方法或类似方法。主程序可以从输入源(键盘/文件)读取数据并决定要播放哪个Key。每个Key可能需要不同的声音。 (至少需要播放不同频率的音符。频率将是SynthNote*内的参数。)

到目前为止一切顺利,除了构建SynthesizerKey个对象时。

class SynthesizerKey
{

protected:

    SynthNoteBase *m_synthnotebase_ptr;

public:

    SynthesizerKey(SynthNoteBase* const synthnote)
        : m_synthnotebase_ptr{synthnote}
    {

    }

    virtual
    ~SynthesizerKey()
    {
        delete m_synthnotebase_ptr; // could be a problem
    }

    void Play()
    {
        m_synthnotebase_ptr->Play(); // or whatever
    }

}

"问题"在调用构造函数时发生。

SynthesizerKey key1(new SynthNoteA(...parameters...)); // had to use new here
// but we never delete! (delete hidden in destructor)

我很欣赏这不是一个真正的问题"本身,但它确实会产生一些非常糟糕的代码,最终用户在构造函数调用中使用new分配内存,但似乎永远不会删除所述内存。

我的问题清楚了吗?这种情况可能有什么选择?

也许这可以使用智能指针解决?我怀疑这只是一个部分解决方案,因为我们可以使用智能指针来隐藏" new"部分以及"删除" part ...实际上不确定它会更好。

SO的想法是什么?

编辑1

(回答以下答案。)

我尝试过这个解决方案:

class SynthesizerKey
{
    std::unique_ptr<SynthNoteBase> m_note;

    SynthesizerKey(const SynthNoteBase& note)
        : m_note(new ...) // ah: can't do this!
        // don't know what type to allocate with new?
    {
    }
}

但是我无法分配内存,因为我将为基类类型而不是派生类类型分配存储空间。

3 个答案:

答案 0 :(得分:0)

要使多态性工作,你别无选择,只能使用指针。

一种可能的解决方案&#34;至少隐藏分配可能是将对基础对象的常量引用作为SynthesizerKey构造函数的参数传递,并处理SynthesizerKey构造函数初始化列表中的内存分配依靠"virtual constructor" (or cloning) pattern复制对象。

另外,我建议您使用std::unique_ptr而不是使用&#34; raw&#34;指针。那么至少你不必担心删除对象。

您的案例中的"virtual constructor" (or cloning) pattern将是这样的:

class SynthNoteBase
{

    // example content
    virtual
    double Sample() = 0;

    virtual SynthNoteBase* clone() = 0;
};

class SynthNoteA : public SynthNoteBase
{
    SynthNoteBase* clone()
    {
        return new SynthNoteA(*this);  // Allocates and invokes the copy-constructor
    }
};

class SynthNoteB : public SynthNoteBase
{
    SynthNoteBase* clone()
    {
        return new SynthNoteB(*this);  // Allocates and invokes the copy-constructor
    }
};

class SynthesizerKey
{
    std::unique_ptr<SynthNoteBase> m_note;

    SynthesizerKey(const SynthNoteBase& note)
        : m_note(note.clone())  // Clone the note, invokes copy-constructor correctly
    {
    }
};

答案 1 :(得分:0)

您可以将注释成员表示为std :: unique_ptr,并通过值或引用传递注释(从SynthNoteBase派生)到SynthesizerKey的构造函数:

#include <memory>
#include <iostream>

class SynthNoteBase
{
    public:
    virtual ~SynthNoteBase() {}
    virtual void Play() const = 0;
};

class SynthNoteA : public SynthNoteBase
{
    public:
    void Play() const override { std::cout << "Play A\n"; };
};

class SynthesizerKey
{
protected:
    std::unique_ptr<SynthNoteBase> m_synthnotebase_ptr;

public:
    template <typename Note>
    SynthesizerKey(Note&& note)
    :   m_synthnotebase_ptr{
            std::make_unique<typename std::decay<Note>::type>(
                std::forward<Note>(note))}
    {}

    void Play() const { m_synthnotebase_ptr->Play();  }
};

int main()
{
    SynthesizerKey keyA{SynthNoteA()};
    keyA.Play();
    return 0;
}

答案 2 :(得分:0)

如果您这样做,请确保您Base CLASS'DESTRUCTOR VIRTUAL TOO!
如果通过Base *删除,则需要确保首先调用派生的析构函数!

您对“必须输入的内容的长度/冗长度”的评论是否“严重可怕”?如果是这样,我不知道你将如何解决它:你需要你需要的东西。

如果这是一种风格化的东西(“有new,哪里是delete?”),请按照以下方式做到这一点的安慰:

Minder(new Child());

意味着你显然正在“放弃”指向Minder的指针来处理它 - 现在是它的问题!

您基本上有三个选项:全对象,引用对象和指向对象的指针。

  1. 整个对象:将对象的引用传递给构造函数,获取本地副本,然后销毁传入的副本。

      

    这里的问题是你不知道传入对象的类型,只知道它是Base,所以任何Base副本都是原始的“切片” - 失去了虚拟。另外:纯方法会阻止整个 - Base个对象。

  2. 引用对象:如果您只记得引用,那么您放弃了管理所述对象内存的选项。

      

    可以delete &reference; - 但是你可能会改为使用指针。

  3. 指向对象:这是您的解决方案,我认为没有任何问题。

  4. 我不相信你应该使用smartptr - 重点是什么?指针只在一个地方;它是唯一可以访问的地方;析构函数将转到delete;问题解决了。

    使用smartptr,其中对象的所有权在对象的生命周期内不清楚。它传递了很多;制作副本;那么什么时候对象终于完成了?在这里,对象的生命周期是完全已知的,在创建时传递一个所有权。不要让它复杂化。

    虚拟构造函数是一种解决方案 - 代价是必须在每个派生类中编写Clone()方法。你仍然需要构造派生对象来传递它 - 让其他人只使用它Clone(),以便你立即破坏它似乎是一项非常大的努力。