Donglei
Donglei

Reputation: 275

A way to implement assignment by 'new'

There are three way to use keyword 'new'. First is the normal way. Suppose Student is a class.

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

Second way . Only ask for the memory space without calling the constructor.

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

Third way is called 'placement new'. Only call the constructor to initialize the meomory space.

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

So, I wrote some code below.

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;
    }
};

This code is ok for gcc. But I'm not sure if it would cause errors or it was dangerous to call destructor explicitly. Call you give me some suggestions?

Upvotes: 2

Views: 184

Answers (3)

James Kanze
James Kanze

Reputation: 153987

Your suggestion is a major anti-pattern: if the new terminates with an exception, you'll get undefined behavior, and if someone tries to derive from your class, all sorts of weird things may occur:

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

and when a goes out of scope, it is the destructor of DerivedStudent which will be called.

As a general rule, if you have to test for self-assignment, your assignment operator is not exception safe.

The goal of this idiom, of course, is to avoid code duplication between the copy constructor and the assignment operator, and to ensure that they have the same semantics. Generally, the best way to do this is to use the swap idiom, or something similar:

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 );
}

(One final, unrelated point. In practice, you're going to run into conflicts with names which begin with an underscore. In general, it's best to avoid leading or trailing underscore.)

Upvotes: 0

utnapistim
utnapistim

Reputation: 27385

But I'm not sure if it would cause errors or it was dangerous to call destructor explicitly. Call you give me some suggestions?

The code as you wrote it has three major drawbacks:

  • it is difficult to read

  • it is optimized for the uncommon case (always performs the self assignment check although you rarely perform self assignment in practice)

  • it is not exception safe

Consider using the copy and swap idiom:

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

This approach is exception-safe if swap doesn't throw and it has two potential drawbacks:

  • if Student is part of a class hierarchy (has virtual members) creating a temporary instance will be more costly.

  • in the case of self assignment the implementation should be a no-op (or a self equality test), but in that case it will create an extra object instance. The self-assignment case is very rare.

Upvotes: 0

Kerrek SB
Kerrek SB

Reputation: 477408

The problem with a "replacement-assignment" is that it's not exception safe. Consider this simplified, generic approach:

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

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

    // ...
};

Now if the copy constructor throws an exception, you're in trouble. You've already called your own destructor, so the inevitable next destructor call will cause undefined behaviour. You also cannot change the order of operations around, since you don't have any other memory.

I actually asked about this sort of "stepping on a landmine" behaviour in a question of mine.

Upvotes: 5

Related Questions