1

好的,非常简单的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最后的函数中。我已经让函数返回 a String,而不是String&故意的,因为它返回的值不应该是左值......

请解释和帮助,谢谢!:)

4

5 回答 5

6

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

于 2012-09-11T16:56:00.603 回答
4

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

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

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

于 2012-09-11T16:51:32.803 回答
2

你的课有几个问题:

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

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

  • conc函数中,您使用分配内存,new[]但随后您使用delete它而不是delete[]导致未定义的行为

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

于 2012-09-11T16:57:22.813 回答
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)) {}

如果您这样做,无论客户端代码是否将分配给您的指针传递给您,它都会正常工作mallocor new

用更多替换strdup和应该很容易,我把它留给你作为练习。freenewdelete

于 2012-09-11T16:50:21.027 回答
1

当您从函数返回一个临时值时,会为返回值制作一个副本,并销毁该临时值。(有时可以通过返回值优化跳过副本,但我认为这里不会发生这种情况)。

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

不可变字符串类的一大好处是,它可以像您在此处所做的那样轻松共享缓冲区,而无需复制开销。您只需要一种机制来计算对缓冲区的引用,以便在删除最后一个对象之前不会删除它。您可以将 astd::shared_ptr自定义删除器一起使用,而不是char *指针。

您还应该研究三法则,以确保您实现了所有必要的功能。

于 2012-09-11T18:59:47.280 回答