继承场景中的 C++ 交换问题

Posted

技术标签:

【中文标题】继承场景中的 C++ 交换问题【英文标题】:C++ swap problem in inheritance scenario 【发布时间】:2009-11-30 22:29:40 【问题描述】:

我想为两个现有的 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 获得代码。

这个用例的出现是因为I want to add swap to boost::multi_array and boost::multi_array_ref。 multi_array 继承自 multi_array_ref。只有将 multi_arrays 与 multi_arrays 交换,将 multi_array_refs 与 multi_array_refs 交换才有意义。

【问题讨论】:

【参考方案1】:

交换,例如赋值和比较适用于值类型,但不适用于类层次结构的基础。

我一直发现最容易遵循 Scott Meyer 的有效 C++ 建议,即不要从具体类派生而只使叶类具体化。然后,您可以安全地将 swap、operator== 等实现为仅叶节点的非虚拟函数。

虽然可以有一个虚拟交换函数,但拥有虚拟基类的全部意义在于在运行时具有动态行为,所以我认为你是一个失败者,试图让所有不正确的可能性在编译时失败。

如果你想走虚拟交换路线,那么一种可能的方法是做这样的事情。

class Base

public:
    virtual void Swap(Base& other) = 0;
;

class ConcreteDerived

    virtual void Swap(Base& other)
    
        // might throw bad_cast, in this case desirable
        ConcreteDerived& cother = dynamic_cast<ConcreteDerived&>(other);bad_cast
        PrivateSwap(cother);
    

    void PrivateSwap(ConcreteDerived& other)
    
        // swap implementation
    
;

【讨论】:

谢谢你的建议,查尔斯。由于我的用例,我需要 Base::Swap 是非虚拟的。我不确定我是否可以采用这一点,因为 Base::Swap 中使用的动态转换类似于 ConcreteDerived::Swap 总是会成功。 如果您的基类 Swap 是非虚拟的,那么您将几乎没有针对切片的保护。您添加到层次结构的任何类都将具有默认的交换操作,该操作将进行切片。如果没有某种多态行为,您将无法通过指针或对它们的基类的引用成功地交换两个派生类。虚函数是最自然的方法。【参考方案2】:

(有点老套的解决方案)

添加一个受保护的虚方法isBaseFoo(),使其在Foo中返回true,在Bar中返回false,Foo的swap方法可以检查它的参数是否有isBaseFoo()==true。

邪恶,仅在运行时检测问题,但我想不出更好的方法,尽管如果您允许 dynamic_cast,Charles Bailey 的答案可能会更好。

【讨论】:

绝对丑陋,但我认为这是唯一可行的做法。谢谢道格拉斯。【参考方案3】:

你不能真正做到这一点,但无论如何我不明白这一点。这并不比在operator= 或复制构造函数上切片更糟糕,而且你也无法避免后者。为什么swap 会有所不同?

出于同样的原因,将swap 设为虚拟可能不值得,与您不将operator= 设为虚拟的原因相同。

【讨论】:

我没有想到这是许多其他地方都存在的标准切片问题。我现在不那么担心了,因为人们可能已经对这类问题感到不满了。【参考方案4】:

我认为这种情况现在可以通过 C++11 中移动语义的存在来解决。 我通常仅使用交换函数来避免分配中的复制重复,因此它仅由需要扩展交换函数并且知道其基的静态类型的派生类静态使用,因此不需要虚拟性(如据说会导致切片的细微问题)。事实上,我在我的基础中将交换函数声明为受保护的方法,以确保它不会直接在其他任何地方使用。 如有必要,最具体的类可以公开最终版本。

【讨论】:

【参考方案5】:

您实际上要做的是从第三方继承层次结构中交换类的实例。鉴于此,我会忘记在实际类上使用交换并添加一个间接级别。使用 boost::shared_ptr 是一个好方法;使用包含您想要的任何类的 shared_ptr 实例,并根据您的意愿进行交换。

一般来说,由于其他回答者描述的所有原因,在编译时解决问题是很困难的。

【讨论】:

以上是关于继承场景中的 C++ 交换问题的主要内容,如果未能解决你的问题,请参考以下文章

C++复制、移动、交换、赋值和析构函数的继承?我需要哪个

C++基础语法继承——万字总结,干货满满

C++基础语法继承——万字总结,干货满满

C++基础语法继承——万字总结,干货满满

C++中的动态继承

是否存在需要多重继承的场景? [关闭]