类似这样的问题千奇百怪。例如:
- 为什么我明明不想复制对象,而编译器却偏偏这么做了呢?
- 如何关闭复制机制?
- 如何防止隐式转换?
- 为何 int 自动转换成了复数?
类的默认复制构造函数和赋值运算符可以复制所有元素。例如:
struct Point {
int x,y;
Point(int xx = 0, int yy = 0) :x(xx), y(yy) { }
};
Point p1(1,2);
Point p2 = p1;
至此,p2.x==p1.x 并且 p2.y==p1.y。这可能正是你想要的(而且也是为了和 C 兼容所必需的),但是,以下代码:
class Handle {
private:
string name;
X* p;
public:
Handle(string n)
:name(n), p(0) { /* acquire X called "name" and let p point to it */ }
~Handle() { delete p; /* release X called "name" */ }
// ...
};
void f(const string& hh)
{
Handle h1(hh);
Handle h1 = h2; // 会引起灾难!
// ...
}
在此,默认复制构造函数使得 h2.name==h1.name 并且 h2.p==h2.p。这将导致一场灾难:当函数 f() 运行结束时,会调用 h1 和 h2 的析构函数,这就导致 h1.p 和 h2.p 所指向的对象被 delete 了两次。
如何避免这场灾难?最简单的办法是,将复制构造函数和赋值运算符声明为私有成员,从而关闭复制机制:
class Handle {
private:
string name;
X* p;
Handle(const Handle&); // 阻止复制
Handle& operator=(const Handle&);
public:
Handle(string n)
:name(n), p(0) { /* acquire the X called "name" and let p point to it */ }
~Handle() { delete p; /* release X called "name" */ }
// ...
};
void f(const string& hh)
{
Handle h1(hh);
Handle h1 = h2; // 编译器报错
// ...
}
如果需要复制机制,我们可以定义自己的复制构造函数和赋值运算符,让它们按我们期待的那样工作。
现在回过头来再看看类 Point。对 Point 来说,可以使用默认的复制机制,但它的构造函数有点问题:
struct Point {
int x,y;
Point(int xx = 0, int yy = 0) :x(xx), y(yy) { }
};
void f(Point);
void g()
{
Point orig; // 使用默认值 (0,0) 创建 orig
Point p1(2); // 使用 yy 的默认值 (0) 来创建 p1
f(2); // 调用 Point(2,0);
}
为了便于创建对象(如这里的 orig 和 p1),我们为 Point 的构造函数提供了默认参数。然后,有些人会感到惊讶的事情发生了:调用 f() 时,2 会转换成 Point(2,0)。当我们定义一个接受单个参数的构造函数时,同时亦定义了一种类型转换方式。默认情况下,类型转换是隐式进行的。若想把类型转换改成 显式进行,就要将构造函数声明为 explicit:
struct Point {
int x,y;
explicit Point(int xx = 0, int yy = 0) :x(xx), y(yy) { }
};
void f(Point);
void g()
{
Point orig; // 使用默认值 (0,0) 创建 orig
Point p1(2); // 使用 yy 的默认值 (0) 来创建 p1
// 显式调用构造函数
f(2); // 错误(试图进行隐式转换)
Point p2 = 2; // 错误(试图进行隐式转换)
Pont p3 = Point(2); // 正确(显式转换)
}
原文地址:http://www.research.att.com/~bs/bs_faq2.html#explicit-ctor
分享到:
相关推荐
所以构造函数不能是虚函数。 2. 从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚...
在上面的代码中,我们定义了一个名为classA的类,其中包括一个默认构造函数、一个带参数的构造函数、一个复制构造函数、一个赋值操作符和一个析构函数。在main函数中,我们使用了多种方式来调用这些函数,例如语句1...
这就是为什么构造函数调用是从基类到更加派生类顺序的另一个理由。 但是,当这一系列构造函数调用正发生时,每个构造函数都已经设置 V PTR 指向它自己的 V TABLE。如果函数调用使用虚机制,它将只产生通过它自己的 ...
C++中一般创建对象,拷贝或赋值的方式有构造函数,拷贝构造函数,赋值函数这三种方法。下面就详细比较下三者之间的区别以及它们的具体实现 1.构造函数 构造函数是一种特殊的类...而默认构造函数没有参数,它什么也不做
首先,我们需要理解什么是复制构造函数。复制构造函数是一个特殊的构造函数,它在创建新对象时,使用已存在的对象作为参数。它的签名通常是`ClassName(const ClassName &other)`,其中`ClassName`是类的名称。复制...
### 构造函数不能声明为虚函数 构造函数在C++中主要用于初始化对象的状态,确保对象在使用前具有有效的初始值。构造函数不能声明为虚函数的原因主要涉及以下几个方面: 1. **对象类型未知**:当创建一个对象时,...
### 构造函数和析构函数在C++中的应用 #### 概述 构造函数与析构函数是C++编程语言中非常重要的概念,它们在类的实例化和销毁过程中扮演着关键角色。通过理解这些函数的工作原理及其作用,我们可以更好地控制对象...
每个类都具有构造函数和析构函数。其中,构造函数在定义对象时被调用,析构函数在对象释放时被调用。如果用户没有提供构造函数和析构函数,系统将提供默认的构造函数和析构函数。 1.构造函数 构造函数是一个与类同名...
没有合适的默认构造函数,默认构造函数怎么写?为什么这样写?没有写赋值构造函数
缺省构造函数是指在定义类时没有定义构造函数的情况下,编译器自动产生的一个构造函数,该函数什么事也不做。其形式为:<类名>::<类名>(){}。 拷贝构造函数是一种特殊的构造函数,它的功能是用一个已知的对象来初始...
在C++编程中,对象的构造过程涉及到多个层面,包括对象成员构造函数、基类构造函数以及派生类本身的构造函数。理解它们的执行顺序对于编写健壮的代码至关重要。以下将详细阐述这三个构造函数的调用时机和逻辑。 1. ...
在C++编程中,派生类继承自基类,它扩展或修改了基类的功能。在创建派生类的对象时,需要确保基类的...在实际开发中,尤其是在设计复杂的系统架构时,理解并正确使用派生类构造函数调用基类构造函数是不可或缺的技能。
基类的构造函数是不能继承的,在声明派生类时,派生类并没有把基类的构造函数继承过来,因此,对继承过来的基类成员初始化的工作也要由派生类的构造函数承担。 在设计派生类的构造函数时,不仅要考虑派生类所增加的...
默认构造函数是没有任何参数的构造函数,它通常用来创建一个不带初始值的对象。例如,在C++中,如果你没有定义任何构造函数,编译器会自动提供一个默认构造函数。默认构造函数的定义如下: ```cpp class MyClass { ...
构造函数与默认构造函数的声明、定义、应用、比较
首先,我们定义了一个名为`A`的基类,它包含一个整型成员变量`x`以及两个构造函数:一个默认构造函数和一个接受整型参数的构造函数。 ```csharp class A { public int x; // 默认构造函数 public A() { x = 0...
在探讨继承中子类与父类构造函数及静态块的执行顺序时,我们首先需要理解构造函数和静态块的基本概念及其在Java中的作用。构造函数主要用于初始化对象的状态,而静态块则是在类加载到内存中时执行的一段代码,通常...
- **特点**:默认构造函数通常用于简单的初始化或者当对象不需要复杂初始化逻辑时。 - **示例**:`Monitor` 类的默认构造函数就是一个例子,它不会对类的属性进行初始化。 ```csharp public class Monitor { ...
无论何时,只要使用new运算符实例化对象,并且不为new提供任何参数,就会调用默认构造函数。如果类不是静态的,C#编译器将为无构造函数的类提供一个公共的默认构造函数,以便该类可以实例化。 私有构造函数可以阻止...