函数返回临时对象行为不端

时间:2012-09-11 16:45:54

标签: c++ string oop function return-value

好的,非常简单的String类,它包含常量字符串(即初始化后无法更改),实现了复制ctor和 连接 功能conc。它的功能给我带来了麻烦,因为我真的无法弄清楚为什么我所做的局部变量不能正常作为返回值传递。

代码:

class String
{
public:
    String(char*);
    String(const String& other);
    ~String();
    friend String conc(const String&, const String&);
    friend std::ostream& operator<<(std::ostream&, const String&);
private:
    const char* const str;
};

String::String(char* aStr) : str(aStr) {}

String::String(const String& other) : str(other.str) {}

std::ostream& operator<<(std::ostream& out, const String& s)
{
    out<<s.str<<"\n";
    return out;
}

String::~String()
{
    delete str;
}

String conc(const String& s1, const String& s2)
{
    int n = strlen(s1.str) + strlen(s2.str);
    int i, j;
    char* newS = new char[n+1];
    for(i = 0; i < strlen(s1.str); ++i)
    {
        newS[i] = s1.str[i];
    }
    for(j = 0; j < strlen(s2.str); ++j)
    {
        newS[i+j] = s2.str[j];
    }
    newS[i+j] = '\0';  //newS is made correctly 
    String tmp(newS); // the tmp object is made correctly
    return tmp;   // here tmp becomes something else --- Why??
}

int main()
{
    String s1("first");
    String s2("SECOND");

    String s3 = conc(s1, s2); //here the copy ctor should be called, right?
    std::cout<<s3;
    _getch();
}

正如您在评论中看到的那样,问题出在最后的conc函数中。我已经使函数返回String而不是String&,因为它返回的值不应该是左值...

请解释&amp;帮助,谢谢!:)

5 个答案:

答案 0 :(得分:6)

这里到处都有内存管理问题。每个String对象都应拥有自己的内存。这意味着String::String(char*)需要分配一个char数组并复制输入字符串的内容; String::String(const String&)需要分配一个char数组并复制输入字符串的内容;并且String::operator=需要删除自己的字符串(除非它与输入字符串相同),分配一个char数组,并复制输入字符串的内容。最后,String::~String()应该delete[] char数组。

答案 1 :(得分:4)

这不是你的想法:问题是删除了temp。您在分配了delete的数组上调用delete[]而不是new[] - 这是一种未定义的行为。

修复该错误后,您将遇到与使用字符串文字初始化的String相关的其他错误:将它们传递给delete[]也是未定义的行为。

问题的根本原因是您的类不允许您区分必须释放的内存和不能释放的内存。您应该统一执行此操作,例如始终将内容复制到您在构造函数中分配的数组中。

答案 2 :(得分:2)

您班上有几个问题:

  • 您的String( char * )构造函数假定传递给它的指针的所有权,因此如果您使用字符串文字构造对象,析构函数将尝试delete它,从而导致未定义的行为。 / p>

  • 您的复制构造函数假定属于正在复制的对象的字符串的所有权,而不是自己的副本,因为该字符串将被双重删除。

  • conc函数中,您使用new[]分配内存,但后来delete代替delete[]导致未定义的行为

  • 成员变量str应该是一个char数组,因此析构函数必须delete[]而不是delete

答案 3 :(得分:1)

您需要更改构造函数,以便它复制C字符串,例如:

String::String(const String& other) : str(strdup(other.str)) {}

如果你使用上面的strdup,你应该适当地更改你的析构函数,所以不要使用delete而是使用free

String::~String() { free(str); }

改变你的其他构造函数是一个好主意,因为它不是获取C字符串,而是复制它,这样所有构造函数的行为通常会更加一致和安全:

String::String(const char* aStr) : str(strdup(aStr)) {}

如果你这样做,无论客户端代码是否通过分配了mallocnew的指针传递,它都能正常工作。

使用更多strdupfree替换newdelete应该很容易,我将其作为练习留给您。

答案 4 :(得分:1)

从函数返回临时值时,会为返回值创建一个副本,并销毁临时值。 (有时可以通过return value optimization跳过副本,但我认为这不会发生。)

因为复制构造函数复制了字符数组指针,所以两个对象现在都指向同一个内存。当临时被销毁时,它会破坏字符数组,现在返回的对象有一个悬空指针。

不可变字符串类的一大好处是,它可以像您一样轻松共享缓冲区,而无需复制的开销。您只需要一种机制来计算对缓冲区的引用,以便在删除最后一个对象之前不会删除它。您可以std::shared_ptr使用a custom deleter代替char *指针。

您还应该查看Rule of Three以确保您正在实施所有必要的功能。