c++11类学习

default/delete 控制默认函数

在我们没有显式定义类的复制构造函数和赋值操作符的情况下,编译器会为我们生成默认的这两个函数:
默认的赋值函数以内存复制的形式完成对象的复制。
这种机制可以为我们节省很多编写复制构造函数和赋值操作符的时间,但是在某些情况下,比如我们不希望对象被复制,
在之前我们需要将复制构造函数和赋值操作符声明为private,现在可以使用delete关键字实现:

class X {
    // …
    X& operator=(const X&) = delete;   // 禁用类的赋值操作符
    X(const X&) = delete;
};

显式地使用default关键字声明使用类的默认行为,对于编译器来说明显是多余的,但是对于代码的阅读者来说,使用default显式地定义复制操作,则意味着这个复制操作就是一个普通的默认的复制操作。

和C++11之前的标准相比,default 关键字在代码可读性上有提升,在控制类成员方法上delete关键字相关private访问权限控制更为直观。

override /final 强制重写/禁止重写虚函数

C++11派生类中可以不实现基类虚函数,也可以实现,但不使用virtual关键字;
这很容易给人造成混淆,有时为了确认某个函数是否是虚函数,我们不得不追溯到基类查看;
C++11引入了两个新的标识符: override和final
override,表示函数应当重写基类中的虚函数。(用于派生类的虚函数中)
final,表示派生类不应当重写这个虚函数。(用于基类中

struct B {
       virtual void f();
       virtual void g() const;
       virtual void h(char);
       void k();      // non-virtual
       virtual void m() final; 
};

struct D : B {
       void f() override;     // OK: 重写 B::f()
       void g() override;     // error: 不同的函数声明,不能重写
       virtual void h(char);  // 重写 B::h( char ); 可能会有警告
       void k() override;     // error: B::k() 不是虚函数
       virtual void m();       // error: m()在基类中声明禁止重写
};

有了这对兄弟,我们的虚函数用起来更为安全,也更好阅读;
c++11在虚函数使用上更为精细,有了明确的使用规范;c++11版本之前对虚函数的使用标识取决于程序员自己的代码风格,较为自由。
有个疑问:父类中不需要子类重写的虚函数为什么不声明成普通成员函数呢?
自己回复:主要是多级继承中使用。final 关键词在父类中声明的虚函数是为了显式说明该函数不会在子类中被重写,如果子类中出现相同函数名、参数列表、返回值时,编译器会报错;这样可以避免无意重写父类虚函数。final关键词作用:第一,它阻止了从类继承;第二,阻止一个虚函数的重载。final 优先于override


委托构造函数 Delegating constructors

委托构造函数解决的问题:C++没有提供让一个构造函数去委托另一个构造函数执行构造操作的机制。这意味着不能(或不提倡)使用缺省参数,类的维护者不得不编写并维护多个构造函数。这会导致源代码和目标代码的重复,降低了可维护性(由于可能引起不一致性),有时还会导致代码膨胀。其它的OO语言,如Java,就提供了这种特性。C++有必要增加相应的特性。

在C++98中,如果你想让两个构造函数完成相似的事情,可以写两个大段代码相同的构造函数,或者是另外定义一个init()函数,让两个构造函数都调用这个init()函数。例如

class X {
        int a;
        // 实现一个初始化函数
        validate(int x) {
            if (0<x && x<=max) a=x; else throw bad_X(x);
        }
    public:
        // 三个构造函数都调用validate(),完成初始化工作
        X(int x) { validate(x); }
        X() { validate(42); }
        X(string s) {
            int x = lexical_cast<int>(s); validate(x);
        }
        // …
    };

这样的实现方式重复罗嗦,并且容易出错。
在C++11中,我们可以在定义一个构造函数时调用另外一个构造函数:

class X {
        int a;
    public:
        X(int x) { if (0<x && x<=max) a=x; else throw bad_X(x); }
        // 构造函数X()调用构造函数X(int x)
        X() :X{42} { }
        // 构造函数X(string s)调用构造函数X(int x)
        X(string s) :X{lexical_cast<int>(s)} { }
        // …
    };

总结:委托构造函数在于可以使用缺省参数进行构造对象。用非委托构造函数A对类成员进行初始化,其他缺省参数构造函数可以直接委托A对来实现对象初始化。eg: X() :X{42} { }

继承的构造函数 Inheriting constructors

若基类拥有数量众多的不同版本的构造函数,而派生类中只有一些成员函数,则对于派生类而言,其构造函数就等同于构造基类。

struct A {
 A(int i) {}
 A(double d, int i) {}
 A(float f, int i, const char* c) {}
 //...
};

struct B : public: A {
 B(int i): A(i) {}
 B(double d, int i): A(d, i) {}
 B(float f, int i, const char* c): A(f, i c) {}
 //...
 virtual void ExtraInterface() {}
};

在c++11中,此方法扩展到构造函数上,子类可以通过using声明来声明继承基类的构造函数。在刚开始的代码可以改造成如下:

struct A {
 A(int i) {}
 A(double d, int i) {}
 A(float f, int i, const char* c) {}
};

struct B : A {
 using A::A;
 virtual void ExtraInterface() {}
};

通过 using A::A的声明,将基类中的构造函数悉数集成到派生类B中。且标准继承构造函数和派生类的各种类默认函数(默认构造、析构、拷贝构造等)一样,是隐式声明的。意味着一个继承构造函数不被相关代码使用,则编译器不会为其产生真正的函数代码。

若基类构造函数含有默认值,则对于继承构造函数来说,参数的默认值不会被继承,但会导致基类产生多个构造函数的版本,而这些函数版本都会被派生类继承。

struct A {
 A(int a = 3, double b = 2.4);
};

struct B : A{
 using A::A;
};

A的构造函数可能有A(int = 3, double = 2.4); A(int = 3); A(const A &); A();则相应地,B中的构造函数也会有:

B(int, double); B(int); B(const B &); B();

注意的问题:

如果基类的构造函数被声明为私有成员函数,或者派生类是从基类中虚继承的,那么就不能够在派生类中声明继承构造函数。且一旦使用继承构造函数,编译器就不会再为派生类生成默认构造函数。

struct A { A(int) {}};

struct B : A { using A::A; };

B b; //B没有默认构造函数

提示:这里统计学习计划的总量
例如:
1、 技术笔记 2 遍
2、CSDN 技术博客 3 篇
3、 学习的 vlog 视频 1 个

类内部成员的初始化 Non-static data member initializers

在C++11之前标准里,只有static const声明的整型成员能在类内部初始化,并且初始化值必须是常量表达式。这些限制确保了初始化操作可以在编译时期进行。

class X {
    static const int m1 = 7;   // 正确
    const int m2 = 7;    // 错误:无static
    static int m3 = 7;              // 错误:无const
    static const string m5 = “odd”; //错误:非整型
};

C++11的基本思想是,允许非静态(non-static)数据成员在其声明处(在其所属类内部)进行初始化。这样,在运行时,需要初始值时构造函数可以使用这个初始值。现在,我们可以这么写:

class A {
public:
    int a = 7;
};
它等同于使用初始化列表:
class A {
public:
    int a;
    A() : a(7) {}
};

在有多个构造函数的类中,其好处就很明显了:

class A {
    public:
         A(): a(7), b(5), hash_algorithm(“MD5″),
           s(“Constructor run”) {}
        A(int a_val) :
          a(a_val), b(5), hash_algorithm(“MD5″),
          s(“Constructor run”)
          {}
        A(D d) : a(7), b(g(d)),
            hash_algorithm(“MD5″), s(“Constructor run”)
            {}
        int a, b;
    private:
        // 哈希加密函数可应用于类A的所有实例
        HashingFunction hash_algorithm;
        std::string s;  // 用以指明对象正处于生命周期内何种状态的字符串
    };

可以简化为:

class A {
    public:
        A() {}
        A(int a_val) : a(a_val) {}
        A(D d) : b(g(d)) {}
        int a = 7;
        int b = 5;
    private:
        //哈希加密函数可应用于类A的所有实例
        HashingFunction hash_algorithm{“MD5″};
        //用以指明对象正处于生命周期内何种状态的字符串
        std::string s{“Constructor run”};;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值