为什么我们必须将const“type”引用而不仅仅是类型名称发送给构造函数

时间:2010-05-21 01:08:55

标签: c++ oop

我试图制作一个简单的程序(& yes,它是一个家庭作业),可以生成日期,&像大多数普通人一样:我将我的Class属性设为私有,我试图将我正在处理的相同类型发送给构造函数但是编译器没有接受它,我做了一些研究&我发现在类似的情况下,人们慷慨地向构造函数发送一个const“类型”引用,这对我来说意味着不太了解OOP 那么为什么我们必须将const“type”引用而不仅仅是类型名称发送给构造函数? &安培;请给我一些初学者链接或网站 这是我的守则的和平:

 class Date {  
     int d ;   
     int m ;   
     int y ;   
 public :   
      Date();   
      Date(int , int , int);   
      Date(const Date &);// my question is : why do we have to write this instead of Date( Date )   
 };

PS:抱歉我的英文

4 个答案:

答案 0 :(得分:6)

用我们的问题来解释:

  

为什么我们必须写Date(const Date &)而不是Date(Date)

我将把它分成两部分,第一部分回答为什么复制构造函数需要为每个引用获取其参数,第二部分为什么它需要是一个const引用。


复制构造函数需要为每个引用获取其参数的原因是,对于一个函数,当你调用它void f(T arg)时,每个副本的参数 f(obj)obj使用arg的复制构造函数将 复制 复制到T 。因此,如果要实现复制构造函数,最好不要通过复制来获取参数,因为这会在调用时调用复制构造函数,从而导致无限递归。您可以自己轻松尝试:

struct tester {
  tester(tester) {std::cout << "inside of erroneous copy ctor\n";}
};

int main()
{
  tester t1;
  std::cout << "about to call erroneous copy ctor\n";
  tester t2(t1);
  std::cout << "done with call erroneous copy ctor\n";
  return 0;
}

该程序应该只写一行然后吹掉堆栈 注意:正如Dennis在评论中指出的那样,实际上这个程序并不能保证编译,因此,根据您的编译器,您可能无法真正尝试它。

底线:复制构造函数应将其参数引用,因为每个副本获取它需要复制构造函数。


这留下了 为何const T&而不仅仅是T& 的问题?事实上,有两个原因 逻辑原因是,当您调用复制构造函数时,您不希望复制的对象发生更改。在C ++中,如果要表达某些东西是不可变的,可以使用const。这告诉用户他们可以安全地将他们珍贵的对象传递给你的拷贝构造函数,因为除了从它读取之外它不会对它做任何事情。作为一个很好的副作用,如果您实现了复制构造函数并意外地尝试写入该对象,编译器会向您抛出一条错误消息,提醒您对调用者的承诺。
另一个原因是您无法将临时对象绑定到非const引用,您只能将它们绑定到const引用。例如,临时对象是函数可能返回的内容:

struct tester {
  tester(tester& rhs) {std::cout << "inside of erroneous copy ctor\n";}
};

tester void f()
{
   tester t;
   return t;
}

当调用f()时,会在里面创建一个tester对象,然后将其副本返回给调用者,调用者可能会将其放入另一个副本中:

tester my_t = f(); // won't compile

问题是f()返回一个临时对象,为了调用复制构造函数,这个临时对象需要绑定到rhs复制构造函数的tester参数,这是一个非const引用。但是您无法将临时对象绑定到非const引用,因此代码将无法编译 虽然您可以根据需要解决此问题(只是不要复制临时文件,而是将其绑定到const引用,这会将临时生命周期延长到引用生命周期的末尾:const tester& my_t = f()) ,人们期望能够复制你的类型的临时工。

底线:复制构造函数应该通过 const reference 来获取其参数,否则用户可能不愿意或无法使用它。


还有一个事实:在下一个C ++标准中,您可以重载临时对象的函数,即所谓的 rvalues 。所以你可以有一个特殊的拷贝构造函数,它使临时对象重载“普通”拷贝构造函数。如果你有一个已经支持这个新功能的编译器,你可以尝试一下:

struct tester {
  tester(const tester&  rhs) { std::cout << "common copy ctor\n"; }
  tester(      tester&& rhs) { std::cout << "copy ctor for rvalues\n"; }
};

使用上述代码调用我们的f()

tester my_t = f();

当将f()调用返回的临时对象复制到my_t并且可能调用常规复制构造函数时,应调用rvalues的新复制构造函数为了将t对象从f()内部复制到返回的临时对象。 (注意:您可能必须禁用编译器的优化才能看到这一点,因为允许编译器优化所有复制。)
那么你有什么用呢?好吧,当你复制一个右值时,你知道复制的对象在调用拷贝构造函数后会被销毁,所以采用右值(T&&)的拷贝构造函数可能只是窃取参数中的值而不是复制它们。既然物体将要被摧毁,没有人会注意到 对于某些类(例如,对于字符串类),值从一个对象移动到另一个对象可能比复制便宜得多。

答案 1 :(得分:4)

如果我正确理解你的问题,避免复制/调用对象的构造函数。

void function(const T&); // does not create new T
void function(T&); // does not create newT, however T must be modifiable (lvalue)
void function(T); // creates new T 

对于简单类型创建新副本是微不足道的(并且通常由编译器优化)。 对于复杂对象,创建新副本可能非常昂贵。 因此,您通过引用传递它。

https://isocpp.org/wiki/faq/references

https://isocpp.org/wiki/faq/ctors

如果你问为什么不能做到以下几点:

struct type {
  type(type);
};

因为这会导致无限递归,因为构造函数依赖于它自己

你可以这样做

struct type {
  type(type, int);
};

因为此构造函数与合成的type(const type&)

不同

http://en.wikipedia.org/wiki/Copy_constructor

答案 2 :(得分:2)

除了@aaa的回答,我将尝试回答const部分。 const部分仅表示您正在逻辑传递的对象不会更改。这是有道理的,因为当使用Date对象参数d调用复制构造函数时,根本不应修改d

您可以移除const,您的代码仍然可以使用相同的方式。但是,const提供了额外的安全性,您永远无法修改标记为const的变量。在您的情况下,这意味着您不能调用Date的任何非const方法。这在编译时由编译器强制执行。

答案 3 :(得分:1)

历史上,这是引入该语言的引用的原因。这是一个解释:

在C中,您可以按值(void f(struct custom_type i))或指针(void g(struct custom_type* i))将值传递给参数。

使用POD值(intchar等)传递值不是问题,但如果您正在查看复杂结构,那么通过放置整个结构,堆栈增长得太快用于函数调用的堆栈。这就是为什么在C中你倾向于通过指针传递结构作为参数,即使函数没有修改它们。

在C ++中,有些情况下两个选项都不起作用:

  • 通过指针传递涉及操作符的反直觉语法(如果为operator +class custom_type custom_type a, b, c; a = &b + &c;定义a是违反直觉的,因为a不获得分配地址的总和。此外,如果您希望能够将值的总和分配给a 地址总和Date(Date d) {},您将必须以某种方式区分案例,通过语法)。

  • 在复制构造函数的情况下,传递值是不可能的或不希望的。在您的情况下,如果您有Date a; Date b(a);和作业a,您获得的是b的副本,只是作为参数传递给a的构造函数。这会导致无限递归,因为创建Date d = a; b = Date(d);作为参数传递的副本与references相同。

由于这些原因(可能还有其他原因),我们决定创建const&:数据类型,它们在语法上看起来像一个值类型,但行为类似于指针(也就是说,它指向的值)另一个变量,但你像变量一样访问它,而不是像指针一样。)

关于你在声明中需要const的原因,你的构造函数将接受临时对象。如果构造函数不接受class Date { public: Date(Date& other); // non-const reference ... ,则无法修改临时引用的值,只能对非const稳定对象使用复制构造函数。

也就是说,如果你有:

Date a;
Date b = a;

你可以写:

Date someFunction() { return Date(xxx); }
Date a = someFunction(); // someFunction returns a temporary object

但不是:

const Date someImportantDate;
Date a = someImportantDate; // cannot pass const value to non-const 

既不:

{{1}}