提问人:TarmoPikaro 提问时间:4/5/2023 最后编辑:TarmoPikaro 更新时间:4/6/2023 访问量:188
如何使复制构造函数也复制虚拟表?
How to make copy constructor copy also virtual table?
问:
下面是简单的代码片段。我想进行类继承,以便 的复制构造函数也可以初始化继承自 的派生类的就地虚拟表。I-A-B
I
I
为简单起见,我们可以假设.sizeof(A) == sizeof(B) == sizeof(I)
#include <iostream>
using namespace std;
class B;
class I
{
public:
virtual ~I()
{
cout << "~I()\n";
}
I()
{
cout << "I ctor\n";
}
virtual void print()
{
cout << "I::print()\n";
}
I(const I& clone);
};
class A : public I
{
public:
~A() override
{
cout << "~A()\n";
}
void print() override
{
cout << "A::print()\n";
}
};
class B : public I
{
public:
B()
{
cout << "B ctor\n";
}
B(const B& clone)
{
cout << "B copy ctor\n";
}
~B() override
{
cout << "~B()\n";
}
void print() override
{
cout << "B::print()\n";
}
};
I::I(const I& clone)
{
cout << "B copy ctor 2\n";
B* b = dynamic_cast<B*>((I*)&clone);
if (b != nullptr)
{
this->~I();
new (this) B(*b);
}
}
class T
{
public:
I GetA()
{
return B();
}
};
int main()
{
cout << "main() ->\n";
T t;
{
cout << "- GetA()\n";
I i = t.GetA();
i.print();
cout << "- before scope end\n";
}
cout << "- main scope\n";
cout << "<- main()\n";
}
这将产生:
main() ->
- GetA()
I ctor
B ctor
B copy ctor 2
~I()
I ctor
B copy ctor
~B()
~I()
I::print()
- before scope end
~I()
- main scope
<- main()
但最后我希望看到被召唤,而不是.~B
~I
main() ->
- GetA()
I ctor
B ctor
B copy ctor 2
~I()
I ctor
B copy ctor
~B()
~I()
B::print() <--- changed
- before scope end
~B() <--- added
~I()
- main scope
<- main()
任何人都可以建议如何使用标准C++解决这个问题?
如果可能的话,我宁愿避免使用内存分配,但允许就地分配。目的是减少对无效指针/内存不足等问题的检查。new
new
答:
好吧,好吧,OP 已经询问了工作原理,并且快速的谷歌搜索并没有出现太多,所以这里有一个快速的纲要。clone idiom
让我们假设:
我们有一个具有一个或多个虚拟函数(纯函数或其他函数)的类。
Base
派生自 的一个或多个类,其中部分或全部覆盖这些函数中的一个或多个。
Base
当我们只有一个可用的指针时,我们需要构造其中一个对象的副本。
Base
普通的旧副本构造函数在这里不会削减它,因此我们需要放置一些脚手架以提供等效的功能。通常的方法是向 添加一个虚拟函数,该函数通常是纯虚拟的(尽管它不一定是)。所以可能看起来像这样:clone
Base
Base
class Base
{
public:
virtual ~Base () { }
virtual std::unique_ptr <Base> clone () = 0;
virtual const char *whoami () { return "base"; }
// ...
};
然后,我们在继承自 的每个类中适当地实现。通常只需调用派生类的复制构造函数就足够了,例如:clone
Base
class Derived : public Base
{
public:
virtual std::unique_ptr <Base> clone () override { return std::make_unique <Derived> (*this); }
const char *whoami () override { return "derived"; }
// ...
}
最后,一个小测试程序:
int main ()
{
Derived d;
Base *b = &d;
auto b2 = b->clone ();
std::cout << b2->whoami ();
}
打印(耶!这种方法的另一个好处是我们可以将参数传递给(我自己经常发现这很有用 - 我有时会传递一组标志位,详细说明要复制的内容)。derived
clone
评论
clone
std:: unique_ptr
接口不能直接使用,因为它是编译器将运行的类型。从理论上讲,可以手动将 vtable 替换为指针,但这不会发生在构造函数调用中,因为 vtable 在成功调用构造函数后已正确初始化。 替换也适用于虚拟方法,但不适用于普通类方法。I
I* (this)
vtable
一种方法是具有单独的访问器类,该类将对 进行操作并将所有操作定向到 。(在下面的示例中称为 )。这可以通过自定义运算符 发生。I*
I*
IC
->
为了避免对内存管理的调用 - 如 / 并处理潜在的内存不足分配失败和检查 - 一种方法是使用就地新构造 或者 - 然后需要分配足够的缓冲区来容纳 或 。new
delete
nullptr
new (buffer) A|B()
new (buffer) A|B(a|b)
sizeof(A)
sizeof(B)
在下面的示例中,如果缓冲区大小不够大,我将用于生成编译时错误。尝试将一些成员字段添加到或查看编译失败。分配更大的阵列,例如 来解决此问题。请注意,调试和发布版本的大小可能不同。static_assert
A
B
char lbuf[20];
示例还显示了仅克隆基类时的零复制/构造/破坏 - 这是通过多个类共享同一指针来实现的。IC i2 = i;
IC
完整示例如下:
#include <iostream>
using namespace std;
class B;
class I
{
public:
virtual ~I()
{
cout << "~I()\n";
}
I()
{
cout << "I ctor\n";
}
virtual void print() = 0;
private:
I(const I& clone) = delete;
};
class IC //interface container
{
// cannot be instantiated on it's own
IC() = delete;
// cannot instantiate from existing pointer (this class does not delete pi pointer)
IC(I* pi) = delete;
public:
// create new instance
IC(int i);
// copy ctor, reuse existing instance
IC(const IC& ic);
// create new instance from local copy
IC(I&& i);
I* operator->()
{
return pI.get();
}
protected:
std::shared_ptr<I> wrap(I* pi);
std::shared_ptr<I> pI;
char ibuf[sizeof(void*) /*hardcode different value as for max( sizeof(A), sizeof(B) ). For pure no-data classes this should be sufficient */];
};
class A : public I
{
public:
A()
{
cout << "A ctor\n";
}
~A() override
{
cout << "~A()\n";
}
A(const A& clone)
{
cout << "A copy ctor\n";
}
void print() override
{
cout << "A::print()\n";
}
};
class B : public I
{
public:
B()
{
cout << "B ctor\n";
}
B(const B& clone)
{
cout << "B copy ctor\n";
}
~B() override
{
cout << "~B()\n";
}
void print() override
{
cout << "B::print()\n";
}
};
std::shared_ptr<I> IC::wrap(I* pi)
{
// we only destruct, but don't delete instance
return shared_ptr<I>(pi,
[](I* pi)
{
pi->~I();
}
);
}
IC::IC(int i)
{
static_assert(sizeof(ibuf) >= sizeof(A), "increase size of ibuf to hold A's value");
static_assert(sizeof(ibuf) >= sizeof(B), "increase size of ibuf to hold B's value");
switch (i)
{
case 0:
pI = wrap(new (ibuf) A);
break;
default:
case 1:
pI = wrap(new (ibuf) B);
break;
}
}
IC::IC(const IC& ic)
{
pI = ic.pI; // Keep reference on same object, don't copy or destruct anything
}
IC::IC(I&& i)
{
if (B* b = dynamic_cast<B*>(&i))
{
pI = wrap(new (ibuf) B(*b));
}
else
{
if (A* a = dynamic_cast<A*>(&i))
{
pI = wrap(new (ibuf) A(*a));
}
}
}
class T
{
public:
IC GetA()
{
return B();
}
};
int main()
{
cout << "main() ->\n";
T t;
{
cout << "- GetA()\n";
IC i = t.GetA();
i->print();
cout << "- clone object\n";
IC i2 = i;
i2->print();
cout << "- before scope end\n";
}
cout << "- main scope\n";
cout << "<- main()\n";
}
它的输出:
main() ->
- GetA()
I ctor
B ctor
I ctor
B copy ctor
~B()
~I()
B::print()
- clone object
B::print()
- before scope end
~B()
~I()
- main scope
<- main()
请注意,polymorphic_value-git 中也存在类似的概念,但需要单独使用标头。
评论
std::variant<A,B>
如果接口的实现集是已知的(如 https://stackoverflow.com/a/75946069 中要求的那样),则可以将繁重的工作委托给 。下面是用作后备存储的该代码的一个版本:std::variant
std::variant
#include <iostream>
#include <memory>
#include <variant>
#include <utility>
using namespace std;
class B;
class I
{
public:
virtual ~I()
{
cout << "~I()\n";
}
I()
{
cout << "I ctor\n";
}
virtual void print() = 0;
private:
I(const I& clone) = delete;
};
class A : public I
{
public:
A()
{
cout << "A ctor\n";
}
~A() override
{
cout << "~A()\n";
}
A(const A& clone)
{
cout << "A copy ctor\n";
}
void print() override
{
cout << "A::print()\n";
}
};
class B : public I
{
public:
B()
{
cout << "B ctor\n";
}
B(const B& clone)
{
cout << "B copy ctor\n";
}
B(B&& clone)
{
cout << "B move ctor\n";
}
~B() override
{
cout << "~B()\n";
}
void print() override
{
cout << "B::print()\n";
}
};
class IC //interface container
{
typedef std::variant<A,B> impl_t;
impl_t impl;
public:
// create new instance
IC(int i);
// copy existing instance
IC(const impl_t& existing) : impl(existing) {}
// move existing instance
IC(impl_t && existing) : impl(std::move(existing)) {}
I* operator->()
{
return std::visit([](auto & impl_) {return static_cast<I*>(&impl_);}, impl);
}
};
IC::IC(int i) : impl(i == 0 ? impl_t(std::in_place_type_t<A>()) :
impl_t(std::in_place_type_t<B>()))
{
}
class T
{
public:
IC GetA1()
{
return IC(1);
}
IC GetA2()
{
return IC(B());
}
};
int main()
{
cout << "main() ->\n";
T t;
{
cout << "- GetA1()\n";
IC i = t.GetA1();
i->print();
cout << "- clone object\n";
IC i2 = i;
i2->print();
cout << "- GetA2()\n";
IC i3 = t.GetA2();
i3->print();
cout << "- before scope end\n";
}
cout << "- main scope\n";
cout << "<- main()\n";
}
评论
->
in_place_type_t
std::inplace_t
std::variant
评论
I
I i = t.GetA();
B
GetA()
I
I
I&
I*
I
this
I
I