使用无赋值交换存在明显的缺陷吗?

5

我正在实现(用于训练目的)一个冒泡排序模板函数:

template<typename iterInput,
         typename predicate>
void BubbleSort(iterInput first1,iterInput last1,predicate func)
{
    bool swapped(false);
    do
    {
        swapped = false;
        iterInput begin = first1;
        iterInput beginMinus = first1;
        ++begin;
        for (;begin != last1; begin++,beginMinus++)
        {
            if (func(*beginMinus,*begin) )
            {
                std::swap(*beginMinus,*begin);
                swapped = true;
            }
        }
    }
    while(swapped);
}

当我意识到这个函数对于没有赋值运算符的类不起作用时,比如下面这个类(请原谅我的糟糕命名):

class NoCopyable
{
public:
    explicit NoCopyable(int value) : value_(value) {}
    NoCopyable(const NoCopyable& other) : value_(other.value_) {}
    ~NoCopyable() {}
    bool operator<(const NoCopyable& other) { return value_ < other.value_; }
    void setValue(int value) { value_ = value; }
    std::ostream& print(std::ostream& os) const { return os << value_; }
private:
    NoCopyable& operator=(const NoCopyable& other);
    int value_;
};

std::ostream& operator<<(std::ostream& os, const NoCopyable& obj)
{
    return obj.print(os);
}

struct PrintNoCopyable
{
    void operator()(const NoCopyable& noCopyable) { std::cout << noCopyable << '\n'; }
};

编译器报错:错误 C2248: 'NoCopyable::operator =' : 无法访问类中声明的私有成员

因此,我稍微修改了代码,使用了我的版本的交换函数而不是 std::swap 函数,以下是代码:

template<typename T1,
         typename T2>
void noAssignmentSwap(T1& t1,T2& t2)
{
    T1 temp(t1);
    t1.~T1();
    new (&t1) T1(t2);
    t2.~T2();
    new (&t2) T2(temp);
}

代码编译并给出正确的结果。然而,我不完全确定,我记得Sutter的一篇文章建议避免玩弄对象的生命周期。该文章只是在没有给出任何真正原因的情况下警告您不要玩火。如果T1或T2的复制构造函数可能会抛出异常,我可以看到异常安全性存在问题。但是,如果允许赋值运算符抛出异常,则标准版本也存在同样的问题。
现在的问题是,您能否看到此版本swap可能存在的任何潜在缺陷?
干杯

显式析构函数调用很少需要... 在我看来有点不可靠。 - Tony The Lion
1
为什么在交换中使用两种不同的类型?如果它们不同,使用一个对象的放置构造函数与另一个对象的地址将会非常糟糕... - 6502
@好的观点,应该只有一个模板参数。 - Alessandro Teruzzi
4个回答

8
除此之外,如果一个类没有赋值运算符,那么它的设计者可能并不打算让它被交换。如果他们这样做了,他们可能也禁用了复制构造函数,因此你的新交换函数仍然无法工作。
至于你声称标准库容器不需要赋值 - 只要你不想对它们进行任何有用的操作,那就是真的。这段代码能编译吗?
#include <vector>
using namespace std;

struct A {
    private:
        void operator=( const A &);
};

int main() {
    vector <A> v;
    v.push_back( A() );
    v[0] = A();     // assignment needed here
}

我认为不会。

如果对象的复制构造函数被禁用,则该对象无法与STL容器一起使用,因此排序的内容就不是那么重要了。 - Alessandro Teruzzi
@Alessandro 所以你正在处理无法赋值但可以复制的对象?根据我的经验,这样的对象非常罕见。而你的问题明确涉及swap()函数。 - user2100815
@Alessandro Teruzzi 标准容器要求其包含的对象无论如何都必须可赋值,因此我同意Neil的说法。 - Mark B
@Alessandro 这取决于您所说的“容器”和“函数”的含义 - 请参见我的编辑。 - user2100815
@High 这样的代码(理所当然地)很少见,正如我所说。事实上,在商业 C++ 编程的 25 年中,我从未编写过这样的代码或遇到过这样的代码(除了在 SO 上的特殊情况)。你的经验可能会有所不同。 - user2100815
显示剩余3条评论

6

不同之处在于当赋值运算符失败时,您仍将拥有相同数量的对象。

如果销毁一个对象并且未能创建新对象,则会丢失一个对象!如果它是容器的一部分,则容器的状态可能也无效。


4
你需要一个复制构造函数而不是赋值运算符,但两者非常相似,以至于在典型情况下,你会同时拥有这两个或者都没有。也就是说,在一般情况下,我认为这并没有什么实际用途。
我认为这与异或交换技巧一样:有趣,但通常没什么用处。

3
可能会使未来代码维护者感到困惑。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接