一个奇怪的问题标题,但我不知道如何紧凑地描述这种情况。
最近我一直在询问有关制作音频合成软件的问题。这个问题涉及一个特定的问题"我有。我的代码编译并正常工作;它只是一个奇怪的"如果你愿意的话。
我有一个包含指向基类的指针的类。我将详细介绍一个最小的示例,以更详细地显示正在发生的事情。
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的想法是什么?
(回答以下答案。)
我尝试过这个解决方案:
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?
{
}
}
但是我无法分配内存,因为我将为基类类型而不是派生类类型分配存储空间。
答案 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
的指针来处理它 - 现在是它的问题!
您基本上有三个选项:全对象,引用对象和指向对象的指针。
整个对象:将对象的引用传递给构造函数,获取本地副本,然后销毁传入的副本。
这里的问题是你不知道传入对象的类型,只知道它是
Base
,所以任何Base
副本都是原始的“切片” - 失去了虚拟。另外:纯方法会阻止整个 -Base
个对象。
引用对象:如果您只记得引用,那么您放弃了管理所述对象内存的选项。
你可以做
delete &reference;
- 但是你可能会改为使用指针。
指向对象:这是您的解决方案,我认为没有任何问题。
我不相信你应该使用smartptr
- 重点是什么?指针只在一个地方;它是唯一可以访问的地方;析构函数将转到delete
;问题解决了。
使用smartptr
,其中对象的所有权在对象的生命周期内不清楚。它传递了很多;制作副本;那么什么时候对象终于完成了?在这里,对象的生命周期是完全已知的,在创建时传递一个所有权。不要让它复杂化。
虚拟构造函数是一种解决方案 - 代价是必须在每个派生类中编写Clone()
方法。你仍然需要构造派生对象来传递它 - 让其他人只使用它Clone()
,以便你立即破坏它似乎是一项非常大的努力。