2

有三种方法可以使用关键字“new”。首先是正常的方式。假设学生是一个班级。

Student *pStu=new Student("Name",age);

第二种方式。只请求内存空间而不调用构造函数。

Student *pArea=(Student*)operator new(sizeof(student));//

第三种方式称为“安置新”。只调用构造函数来初始化内存空间。

new (pArea)Student("Name",age);

所以,我在下面写了一些代码。

class Student
{
private:
    std::string _name;
    int _age;
public:
    Student(std::string name, int age):_name(name), _age(age)
    {
        std::cout<<"in constructor!"<<std::endl;
    }
    ~Student()
    {
        std::cout<<"in destructor!"<<std::endl;
    }
    Student & assign(const Student &stu)
    {
        if(this!=&stu)
        {
            //here! Is it a good way to implement the assignment?
            this->~Student();

            new (this)Student(stu._name,stu._age);

        }
        return *this;
    }
};

此代码适用于 gcc。但我不确定它是否会导致错误或者显式调用析构函数是危险的。打电话给我一些建议?</p>

4

3 回答 3

5

“替换分配”的问题在于它不是异常安全的。考虑这种简化的通用方法:

struct Foo
{
    Foo & operator=(Foo const & rhs)
    {
        if (this == &rhs) { return *this; }

        ~Foo();
        ::new (this) Foo(rhs);   // may throw!
    }

    // ...
};

现在如果复制构造函数抛出异常,你就有麻烦了。您已经调用了自己的析构函数,因此不可避免的下一个析构函数调用将导致未定义的行为。您也无法更改操作顺序,因为您没有任何其他记忆。

我实际上在我的一个问题中询问了这种“踩地雷”行为。

于 2013-07-05T12:31:16.730 回答
0

但我不确定它是否会导致错误或者显式调用析构函数是危险的。打电话给我一些建议?</p>

您编写的代码具有三个主要缺点:

  • 很难阅读

  • 它针对不常见的情况进行了优化(尽管您在实践中很少执行自分配,但始终执行自分配检查)

  • 这不是异常安全的

考虑使用复制和交换习语:

Student & assign(Student stu) // pass stu by value, constructing temp instance
{                             // this is the "copy" part of the idiom
    using namespace std;
    swap(*this, stu); // pass current values to temp instance to be destroyed
                      // and temp values to *this
    return *this;
} // temp goes out of scope thereby destroying previous value of *this

如果 swap 不抛出,这种方法是异常安全的,并且它有两个潜在的缺点:

  • 如果 Student 是类层次结构的一部分(具有虚拟成员),则创建临时实例的成本会更高。

  • 在自赋值的情况下,实现应该是无操作(或自相等测试),但在这种情况下,它将创建一个额外的对象实例。自我分配的情况非常罕见。

于 2013-07-05T13:51:43.890 回答
0

你的建议是一个主要的反模式:如果new终止异常,你会得到未定义的行为,如果有人试图从你的类派生,可能会发生各种奇怪的事情:

DerivedStudent a;
DerivedStudent b;
a = b;      //  Destructs a, and reconstructs it as a Student

a超出范围时, DerivedStudent将调用其析构函数。

作为一般规则,如果您必须测试自赋值,则赋值运算符不是异常安全的。

当然,这个习语的目的是避免复制构造函数和赋值运算符之间的代码重复,并确保它们具有相同的语义。通常,最好的方法是使用交换习语或类似的东西:

Student&
Student::operator=( Student const& other )
{
    Student tmp( other );
    swap( tmp );
    return *this;
}

void
Student::swap( Student& other )
{
    myName.swap( other.myName );
    std::swap( myAge, other.myAge );
}

(最后一点,不相关。在实践中,您会遇到以下划线开头的名称冲突。通常,最好避免前导或尾随下划线。)

于 2013-07-05T13:04:44.110 回答