1

OK、定数String文字列を保持する(つまり、一度初期化すると変更できない)非常に単純なクラスは、copyctorと連結関数を実装します。私が作ったローカル変数が戻り値として正常に渡されない理由を本当に理解できないので、私に問題を与えているのはその関数です。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最後の関数にあります。関数が返す値が左辺値であってはならないことを考慮して、関数が意図的にではなく、を返すようにしましたStringString&

説明して助けてください、ありがとう!:)

4

5 に答える 5

6

ここには、いたるところにメモリ管理の問題があります。各Stringオブジェクトは、独自のメモリを所有する必要があります。つまりString::String(char*)、char の配列を割り当て、入力文字列の内容をコピーする必要があります。String::String(const String&)char の配列を割り当て、入力文字列の内容をコピーする必要があります。String::operator=独自の文字列を削除し (入力文字列と同じでない限り)、char の配列を割り当て、入力文字列の内容をコピーする必要があります。最後に、char の配列がString::~String()必要です。delete[]

于 2012-09-11T16:56:00.603 に答える
4

それはあなたが考えていることではありません: 問題は の削除ですtemp。未定義の動作で割り当てられた配列deleteの代わりに呼び出します。delete[]new[]

そのエラーを修正すると、文字列リテラルで初期化された s に関連する他のエラーが発生します。Stringそれらを に渡すことdelete[]も未定義の動作です。

この問題の根本的な原因は、解放する必要があるメモリと解放してはならないメモリをクラスで区別できないことです。たとえば、コンストラクター内で割り当てる配列にコンテンツを常にコピーするなど、均一に行う必要があります。

于 2012-09-11T16:51:32.803 に答える
2

クラスにはいくつかの問題があります。

  • コンストラクString( char * )ターは、渡されたポインターの所有権を前提としているため、文字列リテラルを使用してオブジェクトを構築すると、デストラクターがdeleteそれを試行し、未定義の動作が発生します。

  • コピー コンストラクターは、文字列が二重に削除されるため、独自のコピーを作成するのではなく、コピーされるオブジェクトに属する文字列の所有権を想定します。

  • 関数では、conc使用してメモリを割り当てますが、未定義の動作につながる代わりにnew[]それを使用しますdeletedelete[]

  • メンバー変数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)) {}

このようにすると、クライアントコードがmallocまたはで割り当てられたポインターを渡しているかどうかに関係なく、正しく機能しますnew

strdupandfreeを more newandに置き換えるのdeleteは簡単なはずなので、演習としてお任せします。

于 2012-09-11T16:50:21.027 に答える
1

関数からテンポラリを返すと、戻り値のコピーが作成され、テンポラリは破棄されます。(戻り値の最適化によってコピーをスキップできる場合もありますが、ここではそうではないと思います)。

コピー コンストラクターは文字配列ポインターをコピーするため、両方のオブジェクトが同じメモリを指しています。一時オブジェクトが破棄されると、文字配列が破棄され、返されたオブジェクトにはダングリング ポインターが含まれるようになります。

不変の文字列クラスの大きな利点の 1 つは、コピーのオーバーヘッドなしで、ここで行うように簡単にバッファーを共有できることです。最後のオブジェクトが削除されるまでバッファが削除されないように、バッファへの参照をカウントするメカニズムが必要なだけです。ポインタの代わりに、カスタムのデリータで astd::shared_ptrを使用できます。char *

また、3 つのルールを調べて、必要な機能をすべて実装していることを確認する必要があります。

于 2012-09-11T18:59:47.280 に答える