广告位联系
返回顶部
分享到

C++特殊类设计概念与示例介绍

C语言 来源:互联网 作者:佚名 发布时间:2023-11-22 22:24:03 人浏览
摘要

一、设计模式概念 设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。 使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可

一、设计模式概念

设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。

根本原因是为了代码复用,增加可维护性。

设计模式的例子:迭代器模式

二、设计一个不能被拷贝的类

拷贝一共就只有两个场景,一个是拷贝构造,一个是赋值运算符重载。所以我们想要设计出一个不能被拷贝的类只需要让外部无法调用这两个函数即可。

在C++98中,我们的方法是将拷贝构造和赋值运算符重载只声明不定义并且将权限设置为私有。

1

2

3

4

5

6

7

8

9

class anti_copy

{

public:

    anti_copy()

    {}

private:

    anti_copy(const anti_copy& ac);

    anti_copy& operator=(const anti_copy& ac);

};

设计原因:

1?? 私有:如果声明成共有,那么就可以在类外面实现定义。

2?? 只声明不定义:因为如果不声明编译器会默认生成这两个的默认成员函数。而不定义是因为该函数不会被调用,就不用写了,这样编译的时候就会出现链接错误。

而在C++11中引入了关键字——delete。

如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。即使权限是共有也无法调用已删除的函数。

1

2

3

4

5

6

7

8

9

class anti_copy

{

public:

    anti_copy()

    {}

    anti_copy(const anti_copy& ac) = delete;

    anti_copy& operator=(const anti_copy& ac) = delete;

private:

};

三、设计一个只能在堆上创建对象的类

3.1 私有构造

首先要把构造函数给私有,不然这个类就可以在任意位置被创建。而构造函数被私有了以后我们怎么创建对象呢?

我们可以在定义一个成员函数,让这个函数在堆上申请空间,但我们知道必须现有对象才能调用成员函数。所以我们就把这个函数设置成静态成员函数。

1

2

3

4

5

6

7

8

9

10

11

class OnlyHeap

{

public:

    static OnlyHeap* GetObj()

    {

        return new OnlyHeap;

    }

private:

    OnlyHeap()

    {}

};

但是这样也不完全对,如果我们这么写:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

class OnlyHeap

{

public:

    static OnlyHeap* GetObj()

    {

        return new OnlyHeap;

    }

private:

    OnlyHeap()

    {}

};

int main()

{

    OnlyHeap* hp1 = OnlyHeap::GetObj();

    OnlyHeap hp2(*hp1);

    return 0;

}

这里的hp2就是栈上的对象。所以我们也要把拷贝构造给封住。

1

2

3

4

5

6

7

8

9

10

11

12

class OnlyHeap

{

public:

    static OnlyHeap* GetObj()

    {

        return new OnlyHeap;

    }

    OnlyHeap(const OnlyHeap& hp) = delete;

private:

    OnlyHeap()

    {}

};

3.2 私有析构

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

class OnlyHeap

{

public:

    OnlyHeap()

    {}

    OnlyHeap(const OnlyHeap& hp) = delete;

private:

    ~OnlyHeap()

    {}

};

int main()

{

    OnlyHeap hp1;// error

    OnlyHeap* hp2 = new OnlyHeap;

    return 0;

}

这里的hp1就不能创建成功,因为对象销毁的时候会调用析构函数,但是这里的析构是私有的,所以该对象无法调用。

但是我们要销毁hp2该怎么办呢?

我们可以定义一个成员函数显示调用析构函数。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

class OnlyHeap

{

public:

    OnlyHeap()

    {}

    OnlyHeap(const OnlyHeap& hp) = delete;

    void Destroy()

    {

        this->~OnlyHeap();

    }

private:

    ~OnlyHeap()

    {}

};

int main()

{

    OnlyHeap* hp2 = new OnlyHeap;

    hp2->Destroy();

    return 0;

}

四、设计一个只能在栈上创建对象的类

为了不让这个类随便定义出对象,首先要把构造函数私有。然后跟上面只能在堆上创建对象的方法相似,定义出一个静态成员函数返回栈上创建的对象。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

class StackOnly

{

public:

    static StackOnly GetObj()

    {

        return StackOnly();

    }

private:

    StackOnly()

    {}

};

int main()

{

    StackOnly hp = StackOnly::GetObj();

    return 0;

}

但是这里有一个问题,无法防止创建静态对象:

1

static StackOnly hp2 = StackOnly::GetObj();

五、设计不能被继承的类

在C++98,为了不让子类继承,我们可以把构造函数私有化,因为子类需要先调用父类的构造函数初始化父类的那一部分成员。

1

2

3

4

5

6

7

class NoInherit

{

public:

private:

    NoInherit()

    {}

};

而在C++11中引入的新的关键字final,被final关键字修饰的类不能被继承。

1

2

3

4

5

class NoInherit final

{

public:

private:

};

六、单例模式

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。

单例模式的特点就是全局只有一个唯一对象。

6.1 饿汉模式

怎么能做到全局只是用一个对象呢,比方说我们现在想要实现一个英汉字典,首先我们要把构造函数私有,不然无法阻止创建对象。然后我们可以在类里面定义一个自己类型的静态成员变量,作用域是全局的。因为对比定义在外边的静态成员变量,内部的可以调用构造函数。

这里要注意把拷贝也要封住。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

class Singleton

{

public:

    static Singleton& GetObj()

    {

        return _s;

    }

    void insert(const std::string& s1, const std::string& s2)

    {

        _dict[s1] = s2;

    }

    void Print()

    {

        for (auto& e : _dict)

        {

            cout << e.first << "->" << e.second << endl;

        }

    }

    // 防拷贝

    Singleton(const Singleton&) = delete;

    Singleton& operator=(const Singleton&) = delete;

private:

    Singleton()

    {}

    std::map<std::string, std::string> _dict;

private:

    static Singleton _s;// 声明

};

Singleton Singleton::_s;// 定义

int main()

{

    Singleton::GetObj().insert("corn", "玉米");

    Singleton& dic1 = Singleton::GetObj();

    dic1.insert("apple", "苹果");

    dic1.insert("banana", "香蕉");

    Singleton& dic2 = Singleton::GetObj();

    dic2.insert("pear", "梨");

    dic2.Print();

    return 0;

}

饿汉模式有什么特点呢?

它会在一开始(main之前)就创建对象。

饿汉模式有什么缺点呢?

1?? 如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。

2?? 多个单例类之间如果有依赖关系饿汉模式就无法控制,比方说要求A类初始化时必须调用B,但是饿汉无法控制先后顺序。

所以针对这些问题,就有了懒汉模式。

6.2 懒汉模式

第一次使用实例对象时,创建对象(用的时候创建)。进程启动无负载。多个单例实例启动顺序自由控制。

我们可以直接对上面饿汉模式的代码进行修改,把静态成员变量变成指针。然后把获取的函数改变一下:

1

2

3

4

5

6

7

8

9

static Singleton& GetObj()

    {

        // 第一次调用才会创建对象

        if (_s == nullptr)

        {

            _s = new Singleton;

        }

        return *_s;

    }

整体代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

class Singleton

{

public:

    static Singleton& GetObj()

    {

        // 第一次调用才会创建对象

        if (_s == nullptr)

        {

            _s = new Singleton;

        }

        return *_s;

    }

    void insert(const std::string& s1, const std::string& s2)

    {

        _dict[s1] = s2;

    }

    void Print()

    {

        for (auto& e : _dict)

        {

            cout << e.first << "->" << e.second << endl;

        }

    }

    // 防拷贝

    Singleton(const Singleton&) = delete;

    Singleton& operator=(const Singleton&) = delete;

private:

    Singleton()

    {}

    std::map<std::string, std::string> _dict;

private:

    static Singleton* _s;// 声明

};

Singleton* Singleton::_s = nullptr;// 定义

6.2.1 线程安全问题

上面的代码存在问题,当多个线程同时调用GetObj(),就会创建多个对象。所以为了线程安全我们要加锁。为了保证锁自动销毁,我们可以自定义一个锁。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

template <class Lock>

class LockAuto

{

public:

    LockAuto(Lock& lk)

        : _lk(lk)

    {

        _lk.lock();

    }

    ~LockAuto()

    {

        _lk.unlock();

    }

private:

    Lock& _lk;

};

class Singleton

{

public:

    static Singleton& GetObj()

    {

        // 第一次调用才会创建对象

        if (_s == nullptr)// 只有第一次才用加锁

        {

            LockAuto<mutex> lock(_mutex);

            if (_s == nullptr)

            {

                _s = new Singleton;

            }

        }

        return *_s;

    }

    void insert(const std::string& s1, const std::string& s2)

    {

        _dict[s1] = s2;

    }

    void Print()

    {

        for (auto& e : _dict)

        {

            cout << e.first << "->" << e.second << endl;

        }

    }

    // 防拷贝

    Singleton(const Singleton&) = delete;

    Singleton& operator=(const Singleton&) = delete;

private:

    Singleton()

    {}

    std::map<std::string, std::string> _dict;

private:

    static Singleton* _s;// 声明

    static mutex _mutex;// 锁

};

Singleton* Singleton::_s = nullptr;// 定义

mutex Singleton::_mutex;// 定义

6.2.2 新写法

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

class Singleton

{

public:

    static Singleton& GetObj()

    {

        static Singleton dic;

        return dic;

    }

    void insert(const std::string& s1, const std::string& s2)

    {

        _dict[s1] = s2;

    }

    void Print()

    {

        for (auto& e : _dict)

        {

            cout << e.first << "->" << e.second << endl;

        }

    }

    // 防拷贝

    Singleton(const Singleton&) = delete;

    Singleton& operator=(const Singleton&) = delete;

private:

    Singleton()

    {}

    std::map<std::string, std::string> _dict;

};

这里就用了静态局部变量只会在第一次定义的时候初始化。在C++11之前是不能保证线程安全的,但是C++11之后就可以了。


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 :
相关文章
  • C++文件IO流及stringstream流读写文件和字符串操作介

    C++文件IO流及stringstream流读写文件和字符串操作介
    一、引入 1 2 3 4 5 6 7 8 9 int main() { string str; while (cin str) { cout str endl; } return 0; } 我们在OJ的时候经常会用到while(cin str),这里的流提取实际上是
  • C++特殊类设计概念与示例介绍

    C++特殊类设计概念与示例介绍
    一、设计模式概念 设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。 使用设计模式的目的:为了代码可重用
  • C++内存对齐的实现方法
    内存对齐的基本原则: 结构(struct/class)的内置类型数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的起始位置要从自身
  • 更优雅的C++字符串格式化实现方法介绍
    在用C++编写代码时,经常需要用到字符串拼接及格式化,尤其是在拼写sql语句时,目前大部分sql拼接方式都是通过ostringstream流一点一点拼接
  • C++模拟实现vector

    C++模拟实现vector
    一、迭代器 定义 vector类型的迭代器就是原生态的指针,对T*进行重命名即可 1 2 typedef T* iterator; typedef const T* const_iterator; 普通迭代器 1 2 3
  • C++模拟实现vector的方法教程
    一、迭代器 定义 vector类型的迭代器就是原生态的指针,对T*进行重命名即可 1 2 typedef T* iterator; typedef const T* const_iterator; 普通迭代器 1 2 3
  • C++实现读写ini配置文件的代码
    1.概述 配置文件的读取是每个程序必备的功能,配置文件的格式多种多样,例如:ini格式、json格式、xml格式等。其中属ini格式最为简单,且
  • C++20中的span容器及用法总结
    一.span容器 span是 C++20 中引入的一个新的标准容器,它用于表示连续的一段内存区间,类似于一个轻量级的只读数组容器。 span是一个轻量级
  • C++20中的std::span介绍
    span就是一个连续对象存储的观察者。类似std::string_view是string的观察者。 连续的存储,不一定是数组。例如: 1 2 3 4 5 6 7 8 zero(char (arr) [10]
  • C++11之std::future对象的使用以及说明

    C++11之std::future对象的使用以及说明
    std::future介绍 在前面几篇文章中基本都用到thread对象,它是C++11中提供异步创建多线程的工具。 但是我们想要从线程中返回异步任务结果,
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计