C++继承场景下的交换问题
我想向两个现有的 C++ 类添加交换功能。一个类继承另一个类。我希望每个类的实例只能与同一类的实例交换。为了使其成为半具体的,假设我有 Foo 和 Bar 类。 Bar 继承自 Foo。我定义了 Foo::swap(Foo&) 和 Bar::swap(Bar&)。 Bar::swap 委托给 Foo::swap。我希望 Foo::swap 仅适用于 Foo 实例,而 Bar::swap 仅适用于 Bar 实例:我不知道如何强制执行此要求。
以下是给我带来麻烦的示例:
#include <algorithm>
#include <iostream>
struct Foo {
int x;
Foo(int x) : x(x) {};
virtual void swap(Foo &other) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
std::swap(this->x, other.x);
};
};
struct Bar : public Foo {
int y;
Bar(int x, int y) : Foo(x), y(y) {};
virtual void swap(Bar &other) {
std::cout << __PRETTY_FUNCTION__ << " ";
Foo::swap(other);
std::swap(this->y, other.y);
};
};
void display(Foo &f1, Foo &f2, Bar &b34, Bar &b56)
{
using namespace std;
cout << "f1: " << f1.x << endl;
cout << "f2: " << f2.x << endl;
cout << "b34: " << b34.x << " " << b34.y << endl;
cout << "b56: " << b56.x << " " << b56.y << endl;
}
int main(int argc, char **argv)
{
{
Foo f1(1), f2(2);
Bar b34(3,4), b56(5,6);
std::cout << std::endl << "Initial values: " << std::endl;
display(f1,f2,b34,b56);
}
{
Foo f1(1), f2(2);
Bar b34(3,4), b56(5,6);
std::cout << std::endl << "After Homogeneous Swap: " << std::endl;
f1.swap(f2); // Desired
b34.swap(b56); // Desired
display(f1,f2,b34,b56);
}
{
Foo f1(1), f2(2);
Bar b34(3,4), b56(5,6);
std::cout << std::endl << "After Heterogeneous Member Swap: " << std::endl;
// b56.swap(f2); // Doesn't compile, excellent
f1.swap(b34); // Want this to not compile, but unsure how
display(f1,f2,b34,b56);
}
return 0;
}
这是输出:
Initial values:
f1: 1
f2: 2
b34: 3 4
b56: 5 6
After Homogeneous Swap:
virtual void Foo::swap(Foo&)
virtual void Bar::swap(Bar&) virtual void Foo::swap(Foo&)
f1: 2
f2: 1
b34: 5 6
b56: 3 4
After Heterogeneous Member Swap:
virtual void Foo::swap(Foo&)
f1: 3
f2: 2
b34: 1 4
b56: 5 6
您可以在最终输出组中看到 f1.swap(b34) 以可能令人讨厌的方式“切片” b34。我希望有罪的行要么不编译,要么在运行时崩溃。由于涉及继承,我认为如果我使用非成员或朋友交换实现,我会遇到同样的问题。
如果有帮助,可以在 codepad 获取该代码。
出现此用例是因为 我想将交换添加到 boost::multi_array 和 boost::multi_array_ref 。 multi_array 继承自 multi_array_ref。只有将 multi_arrays 与 multi_arrays 交换、将 multi_array_refs 与 multi_array_refs 交换才有意义。
I want to add swap functionality to two existing C++ classes. One class inherits from the other. I want each classes' instances to only be swappable with instances of the same class. To make it semi-concrete, say I have classes Foo and Bar. Bar inherits from Foo. I define Foo::swap(Foo&) and Bar::swap(Bar&). Bar::swap delegates to Foo::swap. I want Foo::swap to only work on Foo instances and Bar::swap to only work on Bar instances: I can't figure out how to enforce this requirement.
Here's a sample of what's giving me trouble:
#include <algorithm>
#include <iostream>
struct Foo {
int x;
Foo(int x) : x(x) {};
virtual void swap(Foo &other) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
std::swap(this->x, other.x);
};
};
struct Bar : public Foo {
int y;
Bar(int x, int y) : Foo(x), y(y) {};
virtual void swap(Bar &other) {
std::cout << __PRETTY_FUNCTION__ << " ";
Foo::swap(other);
std::swap(this->y, other.y);
};
};
void display(Foo &f1, Foo &f2, Bar &b34, Bar &b56)
{
using namespace std;
cout << "f1: " << f1.x << endl;
cout << "f2: " << f2.x << endl;
cout << "b34: " << b34.x << " " << b34.y << endl;
cout << "b56: " << b56.x << " " << b56.y << endl;
}
int main(int argc, char **argv)
{
{
Foo f1(1), f2(2);
Bar b34(3,4), b56(5,6);
std::cout << std::endl << "Initial values: " << std::endl;
display(f1,f2,b34,b56);
}
{
Foo f1(1), f2(2);
Bar b34(3,4), b56(5,6);
std::cout << std::endl << "After Homogeneous Swap: " << std::endl;
f1.swap(f2); // Desired
b34.swap(b56); // Desired
display(f1,f2,b34,b56);
}
{
Foo f1(1), f2(2);
Bar b34(3,4), b56(5,6);
std::cout << std::endl << "After Heterogeneous Member Swap: " << std::endl;
// b56.swap(f2); // Doesn't compile, excellent
f1.swap(b34); // Want this to not compile, but unsure how
display(f1,f2,b34,b56);
}
return 0;
}
Here's the output:
Initial values:
f1: 1
f2: 2
b34: 3 4
b56: 5 6
After Homogeneous Swap:
virtual void Foo::swap(Foo&)
virtual void Bar::swap(Bar&) virtual void Foo::swap(Foo&)
f1: 2
f2: 1
b34: 5 6
b56: 3 4
After Heterogeneous Member Swap:
virtual void Foo::swap(Foo&)
f1: 3
f2: 2
b34: 1 4
b56: 5 6
You can see in the final output group where f1.swap(b34) "sliced" b34 in a potentially nasty way. I'd like the guilty line to either not compile or blow up at runtime. Because of the inheritance involved, I think I run into the same problem if I use a nonmember or friend swap implementation.
The code is available at codepad if that helps.
This use case arises because I want to add swap to boost::multi_array and boost::multi_array_ref. multi_array inherits from multi_array_ref. It only makes sense to swap multi_arrays with multi_arrays and multi_array_refs with multi_array_refs.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
交换,就像赋值和比较一样,适用于值类型,但不适用于类层次结构的基础。
我一直发现遵循 Scott Meyer 的有效 C++ 建议最简单,即不从具体类派生并仅将叶类具体化。然后,您可以安全地将 swap、operator== 等实现为仅用于叶节点的非虚函数。
虽然可以拥有虚拟交换函数,但拥有虚拟基类的全部意义在于在运行时具有动态行为,因此我认为您正在尝试在编译时获得所有错误的失败可能性。
如果您想走虚拟交换路线,那么一种可能的方法是执行类似的操作。
Swap, like assignment and comparison work well with value types and don't work well with bases of class hierarchies.
I've always found it easiest to follow the Scott Meyer's Effective C++ recommendation of not deriving from concrete classes and making only leaf classes concrete. You can then safely implement swap, operator==, etc. as non-virtual functions for leaf nodes only.
While it's possible to have a virtual swap functions the whole point of having virtual base classes is to have dynamic behaviour at runtime so I think you're on to a loser trying to get all incorrect possibilities to fail at compile time.
If you want to go the virtual swap route, then one possible approach is to do something like this.
(有点hacky解决方案)
添加一个受保护的虚拟方法isBaseFoo(),让它在Foo中返回true,在Bar中返回false,Foo的swap方法可以检查它的参数是否有isBaseFoo()==true。
邪恶,并且仅在运行时检测问题,但我想不出更好的方法,尽管如果允许dynamic_cast<>,查尔斯·贝利的答案可能会更好。
(Somewhat hacky solution)
Add a protected virtual method, isBaseFoo(), make it return true in Foo, and false in Bar, the the swap method for Foo could check it's argument has isBaseFoo()==true.
Evil, and detects the problem only at run-time, but I can't think of anything better, although Charles Bailey's answer might be better, if you allow dynamic_cast<>.
你确实无法做到这一点,但无论如何我不明白这一点。它并不比在
operator=
或复制构造函数上进行切片更糟糕,而且您也无法避免后者。为什么swap
应该有所不同?出于同样的原因,将
swap
设为虚拟可能不值得,这与不将operator=
设为虚拟的原因相同。You cannot really do this, but I don't see the point, anyway. It's no worse than slicing on
operator=
or copy constructors, and you cannot avoid the latter, either. Why shouldswap
be any different?For the same reason, it's likely not worth it making
swap
virtual, for the same reason why you don't makeoperator=
virtual.我认为这种情况现在可以通过 C++11 中移动语义的存在来解决。
我通常使用交换函数只是为了避免分配中的复制重复,因此它仅由需要扩展交换函数并且知道其基类的静态类型的派生类静态使用,因此不需要虚拟性(即据说可能会导致切片出现微妙的问题)。事实上,我在我的库中将交换函数声明为受保护的方法,以确保它不会在其他地方直接使用。
如果需要的话,最具体的类可以公开最终版本。
I think that this case scenario is now addressed by the presence of move semantics in C++11.
I usually use the swap function only to avoid copy duplication in the assignments so it's only used statically, by the derived class that needs to extend the swap function and that knows the static type of its base so there is no need for virtuality (which as it was stated can lead to subtle problems with slicing). In fact I declare the swap function as a protected method in my base to make sure it's not use directly anywhere else.
The most concrete class can then have the final version public if necessary.
您实际上想要做的是交换第三方继承层次结构中的类实例。鉴于此,我会忘记在实际类上使用交换并添加一定程度的间接性。使用 boost::shared_ptr 是一个很好的方法;使用包含您想要的任何类的shared_ptr实例,并交换到您想要的内容。
一般来说,由于其他回答者描述的所有原因,在编译时解决您提出的问题是很困难的。
What you are actually trying to do is swap instances of classes from a third party inheritance hierarchy. Given that, I'd forget about using swap on the actual classes and add a level of indirection. Using boost::shared_ptr is a good approach; use shared_ptr instances containing whatever class you want, and swap to your heart's content.
In general, solving the problem as you asked it at compile time is hard for all the reasons described by the other answerers.