【问题标题】:C++ Copy constructor and OperatorC++ 复制构造函数和运算符
【发布时间】:2015-06-04 17:54:47
【问题描述】:

我试图理解Copy constructorOperator。我读了一些代码,但我没有得到它。

这里是主要功能

int main()
{
Cwin win1('A',"window")
Cwin win2;
win1 = win2;
win1.set_data('B',"hello");
win1.show();
win2.show();
}

我从Class Cwin

中提取了最重要的代码
Class Cwin
{
  private:
  char id, *title ;

  public:
  Cwin(char i, char *text){......} //constructor
  Cwin(){......}  //default constructor, id ='D' , *title = "default"
  Cwin(const Cwin &win)
  {
    id = win.id;
    strcpy(title,win.title);
  }
  ......
};

输出

B : hello
B : hello

我能理解是什么原因造成的。但我无法理解下面的解决方案。

Class Cwin 
{
   private:
   char id, *title ;

   public:
   Cwin(char i, char *text){......} //constructor
   Cwin(){......}  //default constructor, id ='D' , *title = "default"
   Cwin(const Cwin &win)
   {
     id = win.id;
     strcpy(title,win.title);
    }

   void operator=(const Cwin &win)
   {
     id = win.id;      
     strcpy (this->title , win.title);
   }
   ......


 };

输出:

B : hello
D : default

为什么把 strcpy (title , win.title); 改成 strcpy (this->title , win.title); 会有很大的不同?

【问题讨论】:

  • strcpy (title , win.title) 更改为strcpy (this->title , win.title) 没有任何区别,除非有一个名为title 的变量是函数的本地变量。 stackoverflow.com/help/mcve
  • 它写了一个操作符来重载=,会不会导致这个结果?
  • @CodaChang 我不明白你在问什么,你定义了一个赋值运算符,但它的行为不是你想要的?相反,您期望什么行为?您的复制构造函数似乎正在复制到未初始化的指针 (title),这是未定义的行为。请编辑问题以准确指出问题所在,并如 Benjamin 所说,添加一个 MCVE。
  • 是的,可能。因为没有它,默认赋值运算符只是复制指针,这意味着您的对象将指向相同的数据,并且更改一个指向的字符串的内容也会更改另一个。我感觉你的代码有更严重的问题。但是,由于您对它进行了如此严格的缩写,因此我无法说出您的真实代码实际上是什么,以及您真正在做什么只是简写。这就是为什么我要求MCVE
  • 感谢您的建议,我已经理解这个问题。我会更新我的问题。

标签: c++ class pointers operators copy-constructor


【解决方案1】:

说明:

在成员函数内部,你可以写this->titletitle。两者是等价的,除非您有一个名为 title 的局部函数变量,然后该名称将隐藏该成员。

不同之处在于您添加了一个赋值运算符。

没有赋值运算符,您的语句 win1=win2 通过逐个成员复制来进行。在这种情况下,一个指针被原样复制,这样在复制之后,两个对象都会指向同一个指向的char*

然后set_data() 时,字符串被win1 复制到char* 指针,但win2 指向相同的值。

您的第二个代码 sn-p 可以更好地处理复制:指向的字符串的内容被复制/复制到另一个对象指向的 char*win1win2 因此将继续使用 2 个不同的字符串。

备注/建议

  • 您使用strcpy() 而不检查目标是否足够长以包含复制的字符串。这可能会导致缓冲区溢出、内存损坏和许多其他可怕的事情。

  • 我强烈建议您尽可能使用std::string 而不是char*:默认副本会得到很好的管理,您不必关心内存分配/释放,长度。

【讨论】:

  • +1。我还提倡使用 std::wstring 而不是常规字符串。人们倾向于低估并非所有人都用英语交流的事实。最好从一开始就做好准备。
  • @DavidHaim:wchar_t 不能跨平台移植,这会影响std::wstring。如果您需要支持多个平台,请使用std::u16string 保存UTF-16 字符串,或者使用std::string 保存UTF-8 字符串。
  • 谢谢,这是一个很好的解释和建议。其实我遇到了你指出的bug,我把char改成wstring修复了这个bug
【解决方案2】:

C++ 类有一个默认的赋值运算符,只要你将该类的一个成员分配给另一个成员,它就会执行shallow copy,除非你重载它。 (初始化时除外,它使用复制构造函数)

现在,在第一部分的代码中,没有重载赋值运算符,因此它所做的是将 win2 的元素浅拷贝到 win1,然后拷贝 'id' 和 'title ' 指针(不是它指向的字符串(存储))。 主函数将运行如下:

int main()
{
  Cwin win1('A',"window")
  /*
    This will create a new object win1
    win1.id = 'A' and win1.title will point to "window"
  */
  Cwin win2;
  /*
    This will create a new object win2
    win2.id = 'D' and win2.title will point to "default"
  */

  win1 = win2;
  /*
  This will do somewhat the following
  win1.id = win2.id;
  win1.title = win2.title; ( note that  the pointer is copied )
  Now , win1.id = 'D' win2.id = 'D'
  win1.title points "Defalult"
  win2.title points "Default"
  */

  win1.set_data('B',"hello");
  /*
  This sets the win.id = 'B'
  and now the win1.title points to "hello"
  Also since win1.title and win2.title are both the same pointer
  so win2.title will also point to  "hello"
  */
  win1.show();
  win2.show();
}

但在第二部分中,您重载了赋值运算符,而不是复制指针,而是复制它指向的字符串(存储)。main 现在将按如下方式运行:

int main()
{
  Cwin win1('A',"window")
  /*
    This will create a new object win1
    win1.id = 'A' and win1.title will point to "window"
  */
  Cwin win2;
  /*
    This will create a new object win2
    win2.id = 'D' and win2.title will point to "default"
  */

  win1 = win2;
  /*
  This will now do what is in the overloaded assignment operator
  where to copy the string strcpy is used which will copy the content
  of the string instead of copying the pointers
  Now , win1.id = 'D' win2.id = 'D'
  win1.title points "Defalult"
  win2.title points "Default"
  */

  win1.set_data('B',"hello");
  /*
  This sets the win.id = 'B'
  and now the win1.title points to "hello"
  win2.title will still point to  "Default"
  */
  win1.show();
  win2.show();
}

因此,给定的结果。 您还必须查看并遵循此answer 中给出的建议。

【讨论】:

  • 感谢您的解释,我阅读了这个答案和您的描述,现在我明白了这个问题。你帮了大忙:)
【解决方案3】:

如果Cwin 没有显式实现其自己的赋值运算符(您的第一个示例没有),编译器将生成一个默认实现,该实现只是将成员值按原样从一个对象复制到另一个对象。这就是为什么你的输出对两个对象都说了同样的话——使用默认赋值运算符实现的win1 = win2语句只是用win2.title的指针值覆盖win1.title的指针值,因此title成员被留下指向同一个内存块,随后的win1.set_data() 语句填充了数据。

要执行您的要求,您应该将title 更改为使用std::string 而不是char*,让它为您处理复制。它适用于编译器的默认复制构造函数和赋值运算符实现(除非您有其他需要手动复制的数据):

#include <string>

Class Cwin
{
private:
  char id;
  std::string title;

public:
  Cwin()
    : id('D'), title("default") {}

  Cwin(char i, const std::string &text)
    : id(i), title(text) {}

  void set_data(char i, const std::string &text)
  {
    id = i;
    title = text;
  }

  void show()
  {
    std::cout << id << " : " << title << std::endl;
  }
};

但是,如果您需要直接使用char*,则必须正确实现复制构造函数和赋值运算符,以确保您的title 数据被正确复制,例如:

#include <cstring> 

Class Cwin
{
private:
  char id, *title ;

public:
  Cwin()
    : id(0), title(NULL)
  {
    set_data('D', "default");
  }

  Cwin(char i, char *text)
    : id(0), title(NULL)
  {
    set_data(i, text);
  }

  Cwin(const Cwin &win)
    : id(0), title(NULL)
  {
    set_data(win.id, win.title);
  }

  ~Cwin()
  {
    delete[] title;
  }

  Cwin& operator=(const Cwin &win)
  {
    if (this != &win)
      set_data(win.id, win.title);
    return *this;
  }

  void set_data(char i, char *text)
  {
    int len = std::strlen(text);
    char *newtitle = new char[len+1];
    std::strcpy(newtitle, text);

    delete[] title;
    title = newtitle;
    id = i;
  }

  void show()
  {
    std::cout << id << " : " << title << std::endl;
  }
};

任何时候您必须在构造函数中手动分配内存并在析构函数中释放它,很有可能您还需要在复制构造函数和赋值运算符中复制该数据。阅读有关The rule of three/five/zero 的信息。在这种情况下,CWin 需要遵循 三规则 才能与 char* 正常运行,但在使用 std::string 时可以遵循 零规则 .您应该始终努力编写遵循零规则的代码,这会使事情更容易管理。

【讨论】:

  • 谢谢,所以我需要记住这个概念。正如我看到的指针,我需要实现它的复制构造函数。你帮了大忙。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-09
  • 2020-06-13
  • 2011-07-19
  • 1970-01-01
  • 2020-08-14
  • 2013-09-28
相关资源
最近更新 更多