c++ 构造函数 析构函数

 

构造函数 可以使private。析构函数 也可以 是private

==================

 

===============

C++中构造函数或析构函数定义为private(转)

通常构造函数/析构函数的声明位于public区段,如果在private会有什么样的后果?

那么,private构造函数怎么才能被用到呢?两种方法:

1、使用友元类的对象中的方法来创建它。
2、在本类中实现static方法来创建它。

(1)构造函数定义private

在程序中实例化一个对象,编译器将调用构造函数。如果构造函数是private,由于在class外部不允许访问私有成员,将导致编译失败。

怎么解决这个问题呢?

对于类本身,可以利用static公有成员,因为它独立于class对象之外,不必构造对象就可以使用它们!在某个static函数中创建该class的对象,并以引用或指针的形式将其返回(不以对象返回,主要是构造函数是私有的,外部不能创建临时对象),就获得了这个对象的使用权。

问题:

static成员函数貌似不可以访问非static成员,为什么却可以访问私有的构造函数?

【实例】

class OnlyHeapClass
{
public:
    static OnlyHeapClass* GetInstance()
    {
        // 创建一个OnlyHeapClass对象并返回其指针
        return (new OnlyHeapClass);
    }
   void Destroy();
private:
    OnlyHeapClass() {}
    ~OnlyHeapClass() {}
};
int main()
{
    OnlyHeapClass *p = OnlyHeapClass::GetInstance();
    ... // 使用*p
    delete p;
    return 0;
}

// GetInstance()作为OnlyHeapClass的静态成员函数来在内存中创建对象:由于要跨函数传递并且不能使用值传递方式,所以选择在堆上创建对象,这样即使GetInstance()退出,对象也不会随之释放,可以手动释放。
// 构造函数私有化的类的设计保证了其他类不能从这个类派生或者创建类的实例。

还有这样的用途:

例如,实现这样一个class:它在内存中至多存在一个,或者指定数量个的对象(可以在class的私有域中添加一个static类型的计数器,它的初值置为0,然后在GetInstance()中作些限制:
每次调用它时先检查计数器的值是否已经达到对象个数的上限值,如果是则产生错误,否则才new出新的对象,同时将计数器的值增1。
最后,为了避免值复制时产生新的对象副本,除了将构造函数置为私有外,复制构造函数也要特别声明并置为私有。

如果将构造函数设计成Protected,也可以实现同样的目的,但是可以被继承。(当子类中的构造函数调用父类的private构造函数时会错,当子类中的构造函数调用父类中的 public或protected构造函数时是对的。)

(2)析构函数private

如何保证只能在堆上new一个新的类对象呢?只需把析构函数定义为私有成员。

原因:

C++是一个静态绑定的语言。在编译过程中,所有的非虚函数调用都必须分析完成。即使是虚函数,也需检查可访问性。因些,当在栈上生成对象时,对象会自动析构,也就说析构函数必须可以访问。
而堆上生成对象,由于析构时机由程序员控制,所以不一定需要析构函数。保证了不能在栈上生成对象后,需要证明能在堆上生成它。这里OnlyHeapClass与一般对象唯一的区别在于它的析构函数为私有。

那么如何释放它呢?答案也很简单,提供一个成员函数,完成delete操作。在成员函数中,析构函数是可以访问的。当然detele操作也是可以编译通过。 
void OnlyHeapClass::Destroy() { 
        delete this; 
} 

构造函数私有化的类的设计可以保证只能用new命令在堆中来生成对象,只能动态的去创建对象,这样可以自由的控制对象的生命周期。但是,这样的类需要提供创建和撤销的公共接口。

转载:

https://www.cnblogs.com/vivian187/p/12708262.html
http://www.blogjava.net/fhtdy2004/archive/2009/05/30/278971.html

=================

C++如何让类对象只能在堆(栈)上分配空间

一般情况下写一个类都是可以采用new在堆上分配空间,或直接采用 类名+对象名 的方式在栈上分配空间。但有时候,如果想让一个类只能在栈上或者堆上分配空间,又该怎么实现呢?

下面分别来介绍如何定义只能在栈上或堆上实例化的类。

注:
1.静态实例化对象 如 A a;的步骤为:先由编译器为对象在栈空间分配内存,移动栈顶指针,挪出适当的空间,然后在这个空间上调用构造函数形成一个对象,在对象使用完之后,调用析构函数回收内存,栈顶指针减一。
2.动态实例化对象:new操作符,第一步执行operator new()函数在堆上分配一个内存,然后调用构造函数初始化这片空间。

只能在栈上分配类对象

只有使用new运算符,对象才会建立在堆上,因此要禁用new运算符才能只在栈上分配空间。但new操作符是C++内建的,所以必须要先认清一个事实即:new operator总是先调用operator new,所以我们只要堆new操作符进行重载,并将它声明为private的,就能保证不能再使用new实例化对象,如:

 
 
class A{
private:
    void* operator new(size_t t){}
    void operator delete(void* ptr){}
public:
    A();
    ~A();
};
 

那么此时就不能再调用new操作符了。

只能在堆上实例化对象

容易想到的方法是将构造函数私有化,那么在类外就不能实例化对象,只能在类内提供一个共有函数使用new运算符返回一个对象,这也是典型的单例模式的由来,但在类外还是不能使用new操作符进行实例化。所以这种方法是行不通的。

所以考虑析构函数的私有化。因在栈上实例化对象之后,对象使用完毕之后自动调用析构函数,而new对象后,要手动调用delete函数才能执行对象的析构函数。
所以当析构函数被私有化之后,若在栈上实例化对象,编译器先会检查该对象的析构函数是否可用,如果不可用,则会报错。在堆上释放对象时,若不调用delete就不会发现析构函数不可访问。

这就会引发另外一个问题,使用new操作符在堆上实例化的对象要怎么析构呢?解决办法就是,在类中自定义一个公有函数用来销毁对象,该函数调用delete操作符,这样就可以使用该函数销毁对象而不是直接使用delete操作符了。
如:

 
 
class A{
public:
    A(){}
    void destroy(){delete this;}
private:
    ~A(){}

};
 

在此,就可以使用new在堆上创建对象而使用destroy()析构对象了。

注意:采用这种方式后,类A不能用在继承体系当中。
若A为基类,那么其析构函数就是virtual的(保证内存完全释放),子类必须要能重写该析构函数,但子类根本无法访问他,这是其一。其二,即使子类能够重写析构函数,若B是继承自A的子类,且声明了一个B类的对象b,那么此时若析构B,基类A不能被正确释放,导致内存泄露。

https://www.cnblogs.com/lixuejian/p/12573185.html

==

c++ 构造函数 与析构函数 特点 与区别:

在C++中,构造函数和析构函数是特殊的成员函数,负责对象的初始化和销毁。它们在使用上有一些特点和区别。

构造函数 (Constructor)

特点:

  1. 名称与类名相同:构造函数的名称必须与类名相同,没有返回类型(包括void)。
  2. 自动调用:当创建对象时,构造函数会被自动调用。
  3. 可以重载:一个类可以有多个构造函数(重载),这些构造函数必须有不同的参数列表。
  4. 初始化列表:可以使用初始化列表对成员变量进行初始化。
  5. 默认构造函数:如果没有定义构造函数,编译器会提供一个默认的无参数构造函数。

示例:

cpp
class MyClass { public: int a; MyClass() : a(0) { // 默认构造函数 // 初始化代码 } MyClass(int val) : a(val) { // 带参数的构造函数 // 初始化代码 } };

析构函数 (Destructor)

特点:

  1. 名称与类名相同,但前面加~号:析构函数的名称是类名前加上波浪号(~),没有返回类型(包括void),也没有参数。
  2. 自动调用:当对象的生命周期结束时,析构函数会被自动调用,例如当对象离开作用域或使用delete删除时。
  3. 不可重载:一个类只能有一个析构函数,不能重载。
  4. 执行清理任务:析构函数通常用于释放资源,如内存、文件句柄等。

示例:

cpp
class MyClass { public: int* p; MyClass() { p = new int; // 分配内存 } ~MyClass() { delete p; // 释放内存 } };

=================

=================

=================

1.简介
子类为完成基类初始化,在 C++11 之前,需要在初始化列表调用基类的构造函数,从而完成构造函数的传递。如果基类拥有多个构造函数,那么子类也需要实现多个与基类构造函数对应的构造函数。

class Base {
public:
Base(int v): _value(v), _c(‘0’){}
Base(char c): _value(0), _c(c){}
private:
int _value;
char _c;
};

class Derived: public Base {
public:
// 初始化基类需要透传参数至基类的各个构造函数,非常麻烦
Derived(int v) :Base(v) {}
Derived(char c) :Base(c) {}

// 假设派生类只是添加了一个普通的函数
void display() {
// dosomething
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
书写多个派生类构造函数只为传递参数完成基类初始化,这种方式无疑给开发人员带来麻烦,降低了编码效率。从 C++11 开始,推出了继承构造函数(Inheriting Constructor),使用 using 来声明继承基类的构造函数,我们可以这样书写。

class Base {
public:
Base(int v) :_value(v), _c('0'){}
Base(char c): _value(0), _c(c){}
private:
int _value;
char _c;
};

class Derived: public Base {
public:
// 使用继承构造函数
using Base::Base;

// 假设派生类只是添加了一个普通的函数
void display() {
// do something
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
我们通过 using Base::Base 把基类构造函数继承到派生类中,不再需要书写多个派生类构造函数来完成基类的初始化。

更为巧妙的是,C++11 标准规定,继承构造函数与类的一些默认函数(默认构造、析构、拷贝构造函数等)一样,是隐式声明,如果一个继承构造函数不被相关代码使用,编译器不会为其产生真正的函数代码。这样比通过派生类构造函数“透传构造函数参数”来完成基类初始化的方式,总是需要定义派生类的各种构造函数更加节省目标代码空间。

2.注意事项
(1)继承构造函数无法初始化派生类数据成员。

这个很好理解,因为继承构造函数的功能是初始化基类,对于派生类数据成员的初始化则无能为力。解决的办法主要有两个:

一是使用 C++11 特性就地初始化成员变量,可以通过 =、{} 对非静态成员快速就地初始化,以减少多个构造函数重复初始化变量的工作,注意初始化列表会覆盖就地初始化操作。

class Derived: public Base {
public:
// 使用继承构造函数
using Base::Base;

// 假设派生类只是添加了一个普通的函数
void display() {
// do something
}
private:
// 派生类新增数据成员
double _double{0.0};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
二是新增派生类构造函数,使用构造函数初始化列表。

class Derived :public Base {
public:
// 使用继承构造函数
using Base::Base;

// 新增派生类构造函数
Derived(int a, double b):Base(a), _double(b){}

// 假设派生类只是添加了一个普通的函数
void display() {
// do something
}
private:
// 派生类新增数据成员
double _double{0.0};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
相比之下,第二种方法需要新增构造函数,明显没有第一种方法简洁,但第二种方法可由用户控制初始化值,更加灵活。各有优劣,两种方法需结合具体场景使用。

(2)构造函数拥有默认值会产生多个构造函数版本,且继承构造函数无法继承基类构造函数的默认参数,所以我们在使用有默认参数构造函数的基类时必须要小心。

class A {
public:
A(int a = 3, double b = 4): _a(a), _b(b){}
void display() {
cout<<_a<<" "<<_b<<endl;
}

private:
int _a;
double _b;
};

class B:public A {
public:
using A::A;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
那么 A 中的构造函数会有下面几个版本:

A()
A(int)
A(int, double)
A(const A&)
1
2
3
4
那么 B 中对应的继承构造函数将会有如下几个版本:

B()
B(int)
B(int, double)
B(const B&)
1
2
3
4
可以看出,参数默认值会导致多个构造函数版本的产生,因此在使用时需格外小心。

(3)多继承的情况下,继承构造函数会出现“冲突”的情况,因为多个基类中的部分构造函数可能导致派生类中的继承构造函数的函数签名(函数名与参数)相同。

class A {
public:
A(int i){}
};

class B {
public:
B(int i){}
};

class C : public A, public B {
public:
using A::A;
using B::B; //编译出错,重复定义C(int)

// 显示定义继承构造函数 C(int)
C(int i):A(i),B(i){}
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
为避免继承构造函数冲突,可以通过显示定义来阻止隐式生成的继承构造函数。

此外,使用继承构造函数还需要注意:如果基类构造函数被申明为私有成员函数,或者派生类是从虚基类继承而来 ,那么就不能在派生类中申明继承构造函数
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/K346K346/article/details/81703914

=========

构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。例如:

class CExample {
public:
    int a;
    float b;
    //构造函数初始化列表
    CExample(): a(0),b(8.8)
    {}
    //构造函数内部赋值
    CExample()
    {
        a=0;
        b=8.8;
    }
};

上面的例子中两个构造函数的结果是一样的。上面的构造函数(使用初始化列表的构造函数)显式的初始化类的成员;而没使用初始化列表的构造函数是对类的成员赋值,并没有进行显式的初始化。

初始化和赋值对内置类型的成员没有什么大的区别,像上面的任一个构造函数都可以。对非内置类型成员变量,为了避免两次构造,推荐使用类构造函数初始化列表。但有的时候必须用带有初始化列表的构造函数:

  • 1.成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
  • 2.const 成员引用类型的成员。因为 const 对象或引用类型只能初始化,不能对他们赋值。

初始化数据成员与对数据成员赋值的含义是什么?有什么区别?

首先把数据成员按类型分类并分情况说明:

  • 1.内置数据类型,复合类型(指针,引用)- 在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的
  • 2.用户定义类型(类类型)- 结果上相同,但是性能上存在很大的差别。因为类类型的数据成员对象在进入函数体前已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又调用个拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)

注意点:

初始化列表的成员初始化顺序:

C++ 初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。

class CMyClass {
    CMyClass(int x, int y);
    int m_x;
    int m_y;
};

CMyClass::CMyClass(int x, int y) : m_y(y), m_x(m_y)
{
};

你可能以为上面的代码将会首先做 m_y=I,然后做 m_x=m_y,最后它们有相同的值。但是编译器先初始化 m_x,然后是 m_y,,因为它们是按这样的顺序声明的。结果是 m_x 将有一个不可预测的值。有两种方法避免它,一个是总是按照你希望它们被初始化的顺序声明成员,第二个是,如果你决定使用初始化列表,总是按照它们声明的顺序罗列这些成员。这将有助于消除混淆。

原文地址:http://www.cnblogs.com/BlueTzar/articles/1223169.html

===================

在C++中,类的构造函数可以使用以下两种方式进行初始化:

构造函数的初始化列表(Initialization List)
构造函数的初始化列表可以在构造函数的参数列表后面使用冒号分隔符,然后在冒号后面列出每个成员变量及其对应的初始化值。例如:

class MyClass
{
public:
MyClass(int value) : m_value(value) {}

private:
int m_value;
};
1
2
3
4
5
6
7
8
在这个例子中,构造函数使用了初始化列表来初始化m_value成员变量。

在构造函数体内赋值(Assignment in Constructor Body)
构造函数也可以在其函数体内使用赋值语句来初始化成员变量。例如:

class MyClass
{
public:
MyClass(int value)
{
m_value = value;
}

private:
int m_value;
};
1
2
3
4
5
6
7
8
9
10
11
在这个例子中,构造函数使用了赋值语句来初始化m_value成员变量。

需要注意的是,使用初始化列表进行成员变量初始化比在构造函数体内使用赋值语句效率更高,并且可以用于初始化const成员变量或引用类型成员变量
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/Aheaboy/article/details/130513295

=================

构造函数: 一种特殊的成员函数,主要用于为对象分配空间,进行初始化。

构造函数的名字必须与类名相同,而不能由用户任意命名。
可以有任意类型的参数,但不能具有返回值类型。
不需要用户来调用,而是在建立对象时自动执行的。
析构函数: 执行与构造函数相反的操作,通常用于执行一些清理任务,如释放分配给对象的内存空间,在写C++程序函数时每次 return 0; 之后,会自动的调用析构函数释放内存空间。

析构函数名与类名相同,但它前面必须加一个波浪号(~)。
析构函数不返回任何返回值。在定义析构函数时,是不能说明它的类型的,甚至说明void类型也不行。
析构函数没有参数,所以说是不能被重载的。
析构函数只有一个。
撤销对象时,编译系统会自动地调用析构函数。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_43619271/article/details/123561402

=============

 

参考:

https://blog.csdn.net/Aheaboy/article/details/130513295

posted @ 2024-05-29 15:37  redrobot  阅读(67)  评论(0编辑  收藏  举报