理解C++动态内存分配

3

Consider the following code:

class CString
{
private:
    char* buff;
    size_t len;

public:
    CString(const char* p):len(0), buff(nullptr)
    {
        cout << "Constructor called!"<<endl;
        if (p!=nullptr)
        {
            len= strlen(p);
            if (len>0)
            {
                buff= new char[len+1];
                strcpy_s(buff, len+1, p);               
            }           
        }       
    }

    CString (const CString& s)
    {
        cout << "Copy constructor called!"<<endl;
        len= s.len;
        buff= new char[len+1];
        strcpy_s(buff, len+1, s.buff);      
    }

    CString& operator = (const CString& rhs)
    {
        cout << "Assignment operator called!"<<endl;
        if (this != &rhs)
        {
            len= rhs.len;
            delete[] buff;          
            buff= new char[len+1];
            strcpy_s(buff, len+1, rhs.buff);
        }

        return *this;
    }

    CString operator + (const CString& rhs) const
    {
        cout << "Addition operator called!"<<endl;

        size_t lenght= len+rhs.len+1;
        char* tmp = new char[lenght];
        strcpy_s(tmp, lenght, buff);
        strcat_s(tmp, lenght, rhs.buff);

        return CString(tmp);
    }

    ~CString()
    {
        cout << "Destructor called!"<<endl;
        delete[] buff;
    }     
};

int main()
{
CString s1("Hello");
CString s2("World");
CString s3 = s1+s2;     
}

我的问题是我不知道如何删除在加法运算符函数中分配的内存(char* tmp = new char[length])。我不能在构造函数中删除它(我尝试过delete[] p),因为它还会被从主函数调用,其中使用的字符数组参数未分配在堆上...我该怎么解决这个问题?


你还需要处理你的赋值运算符(它不安全,最好使用复制并交换惯用法)。请参见下面。 - Martin York
4个回答

4

添加函数应该返回一个CString,而不是一个CString&。在添加函数中,您应该构造返回值,然后删除[] temp,因为在CString类内部,您进行了内存复制,所以它不再需要。

CString operator + (const CString& rhs) const
{
    cout << "Addition operator called!"<<endl;

    size_t lenght= len+rhs.len+1;
    char* tmp = new char[lenght];
    strcpy_s(tmp, lenght, buff);
    strcat_s(tmp, lenght, rhs.buff);

    CString retval(tmp);
    delete[] tmp;
    return retval;
}

所以你在非静态方法中创建了一个新的CString实例?我认为这不是一个很好的实现。 - trojanfoe
2
@trojanfoe:这就是operator+通常的实现方式。除了创建一个新的CString,你还能怎样返回它呢? - Puppy
@DeadMG 非常感谢!原来很简单,我只需要创建对象而不立即返回它,以便有时间删除数组! - kiokko89
@DeadMG:是的,我的实现是“operator + =”...哎呀 :) - trojanfoe
我建议创建一个私有的构造函数,接受一个额外的参数,指明是否传递所有权。这样你就可以避免额外的动态分配/复制/释放循环,并更容易地在operator+中实现RVO。 - rjnilsson
显示剩余3条评论

2
问题:
在你的赋值运算符中,你没有提供任何异常保证。在你保证操作成功之前,你就删除了缓冲区。如果出现问题,你的对象将处于未定义状态。
CString& operator = (const CString& rhs)
{
    cout << "Assignment operator called!"<<endl;
    if (this != &rhs)
    {
        len= rhs.len;
        delete[] buff;          
        buff= new char[len+1];   /// BOOM 

        // If you throw here buff now points at undefined memory.
        // If this is an automatic variable the destructor is still going
        // to be called and you will get a double delete.

        // All operations that can fail should be done BEFORE the object is modified.

        strcpy_s(buff, len+1, rhs.buff);
    }

    return *this;
}

我们可以通过移动事物(并使用临时变量)来纠正这些问题。
CString& operator = (const CString& rhs)
{
    cout << "Assignment operator called!"<<endl;
    if (this != &rhs)
    {
        char* tmp = new char[len+1];
        strcpy_s(tmp, rhs.len+1, rhs.buff); // for char this will never fail
                                            // But if it was another type the copy
                                            // may potentially fail. So you must
                                            // do the copy before changing the curren
                                            // objects state.

        // Now we can change the state of the object safely.
        len= rhs.len;
        std::swap(tmp,buff);

        delete tmp;
    }

    return *this;
}

更好的解决方案是使用复制并交换惯用语:
CString& operator = (CString rhs) // Note pass by value to get auto copy.
{                                 // Most compilers will then do NRVO
    this->swap(rhs);
    // Simply swap the tmp rhs with this.
    // Note that tmp was created with copy constructor.
    // When rhs goes out of scope it will delete the object.
}

void swap(CString& rhs)
{
    std::swap(len,  rhs.len);
    std::swap(buff, rhs.buff);
}

现在让我们来处理您的+运算符。
CString operator + (const CString& rhs) const
{
    // You could optimize this by providing a private constructor
    // that takes two char pointers so that allocation is only done
    // once.
    CString result(*this);
    return result += rhs;
}

CString operator += (const CString& rhs)
{
    size_t lenght= len+rhs.len+1;

    // Char are easy. No chance of failure.
    // But if this was a type with a copy constructor or any other complex
    // processing involved in the copy then I would make tmp a smart pointer
    // to make sure that it's memory was not leaked if there was an exception.
    char* tmp = new char[lenght];

    strcpy_s(tmp, lenght, buff);
    strcat_s(tmp, lenght, rhs.buff);

    std::swap(len, length);
    std::swap(buff, tmp);

    delete tmp;
}

非常感谢您的解释。虽然我只是一个初学者,但您给了我一些很好的提示,让我理解这些事情应该如何完成... - kiokko89

0
    CString& operator + (const CString& rhs) const

{
    cout << "Addition operator called!"<<endl;

    size_t lenght= len+rhs.len+1;
    char* tmp = new char[lenght];
    strcpy_s(tmp, lenght, buff);
    strcat_s(tmp, lenght, rhs.buff);
    CString tempObj(tmp);
    delete [] tmp;
    return tempObj;
}

例如,

这是我的错误...我拼错了返回类型,应该是CString而不是CString&(现在我已经编辑了帖子)。无论如何,你们所有人都对我有很大的帮助...谢谢! - kiokko89

0
首先,operator+ 应该返回一个新的对象,而不是修改 + 的操作数之一,因此最好将其声明为非成员(可能是友元)函数。首先实现 operator+=,然后使用它 - operator+,你就不会有这个问题了。
CString operator+(CString const& lh, CString const& rh)
{
    CString res(lh);
    return res += rh;
}

1
一个小优化:你可以通过值传递lh。这将为您提供副本,而无需实际创建临时副本。(我知道这会使阅读变得更加困难(当我这样做时,我总是添加有关按值传递的注释)),但它也有助于编译器进行NRVO。我相信GMAN写了一篇关于这个主题的优秀文章。@Gman如果您能指出来,我找不到它。 - Martin York

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