C++基础:函数声明

1 删除函数

使用“= delete”将复制构造函数和复制赋值运算符标识为删除函数。

优先使用删除函数,而非private未定义函数。

习惯上,删除函数会被声明为public,而不是private。

这是因为客户代码尝试使用某个成员函数时会先校验可访问性,再校验删除状态。这样一来,当客户代码试图调用某个private删除函数时,编译器只会报错该函数为private,这样得到的错误信息并不完善。

bool isLucky(int number);

bool isLucky(char) = delete;
bool isLucky(bool) = delete;
bool isLucky(double) = delete;   // 拒绝float和double

对于模板:

class Widget {
public:
    ...
    template<typename T>
    void processPointer(T* ptr)
    { ... }
    ...

};

template<>
void Widget::processPointer<void>(void*) = delete;

1.2 要点

优先使用删除函数,而非private未定义函数。

任何函数都可以删除,包括非成员函数和模板具现。

2 override

虚函数改写,使得通过基类接口调用派生类函数成为可能。

class Widget {
public:
    ...
    void doWork() &;        // 这个版本的doWork仅在*this是左值时调用
    void doWork() &&;       // 这个版本的doWork仅在*this是右值时调用
};

Widget makeWidget();
Widget w;

w.doWork();                 // 以左值调用Widget::doWork()
makeWidget().doWork();      // 以右值调用Widget::doWork()

如果基类中的虚函数带有引用饰词,则派生类要对该函数进行改写版本必须也带有完全相同的引用饰词。

2.2 要点

为意在改写的函数添加override声明

3 noexcept

对不会抛出异常的函数应用noexcept。

3.1 与c++98的throw()对比

int f(int x) throw();   // f不会抛出异常:C++98版本

int f(int x) noexcept;  // f不会抛出异常:C++11版本

在C++98异常规格下,调用栈会解开至f的调用方,然后执行一些与本条款无关的动作后,程序终止。

在C++11的异常规格下,运行期行为会稍有不同:程序执行中止前,栈只是可能会开解。(优化器不需要在异常传出函数的前提下,将执行期保持在可开解的状态;也不需要在异常被函数抛出的前提下,保证所有其中的对象以其被构造顺序的逆序完成析构)

3.2 要点

noexcept声明是函数接口的组成部分,这意味着调用方可能会对它有依赖。

相对于不带noexcept声明的函数,带有noexcept声明的函数有更多机会得到优化。

noexcept性质对于移动操作、swap、内存释放函数和析构函数最有价值。

大多数函数都是异常中立的,不具备noexcept性质。

4 constexpr

除了修饰在变量前,表示编译期常量,也可以修饰在函数。

若传给一个constexpr函数的实参值是在编译器已知的,其结果也会在编译期间计算出来,若传入的值有一个或多个是未知的,则结果也会在运行期执行返回。

class Point
{
public:
    constexpr Point(double xVal = 0, double yVal = 0) noexcept : x(xVal), y(yVal)
    {};

    constexpr double xValue() const noexcept { return x; };
    constexpr double yValue() const noexcept { return y; };
    constexpr void setX(double newX) noexcept { x = newX; };  // c++ 14将void视为字面类型,设置器也可以加上constexpr
    constexpr void setY(double newY) noexcept { y = newY; };

private:
    double x, y;
}

constexpr
Point midpoint() noexcept
{
    return { (p1.xValue() + p2.xValue()) / 2,
             (p1.yValue() + p2.yValue()) / 2};
}

constexpr auto mid = midpoint(p1, p2);
constexpr Point p1(9.4, 27.8);   // 构造函数声明为constexpr, 入参编译期可知,构造结果也是编译期可知

4.2 要点

constexpr对象都具备const属性,并由编译期已知的值完成初始化。

constexpr函数在调用时若传入的实参值是编译器已知,则会产出编译器结果。

比起非constexpr对象或constexpr函数,constexpr对象或constexpr函数可以用在一个作用域更广的语境中。

5 const

const函数表示这个函数的入参和返回都是常量

const函数的线程安全性需要被保证(否则不同的多个线程在没有同步的情况下读写一块内存,会导致该const函数的返回值被改变(获取多项式的根并不会导致该多项式改变其值)),因此可以使用锁std::mutex或者原子变量std::atomic

class Polynomial {
public:
  using RootsType = std::vector<double>;
  RootsType roots() const
  {
     std::lok_guard<std::mutex> g(m);      // 添加互斥量
     if (!rootsAreValid) {                 // 若缓存无效则计算/存储roots
         ...
         rootsAreValid = true;
     }
     return rootVals;
  }                                        // 解锁互斥量

private:
  mutable std::mutex m;
  mutable bool rootsAreValid{ false };
  mutable RootsType rootVals{};
}

添加mutable是因为加锁和解锁都不是const成员函数所为,如果不加mutable,m在roots内会被当作一个const对象处理。

(std::mutex是一个只移型别,只能移动不能复制。将m加入Polynomial之后,Polynomial也成为只移型别)

class Widget
{
public:
    ...
    int magicValue() const
    {
        if (cacheValid) return cachedValue;
        else {
            auto val1 = expensiveComputation1();
            auto val2 = expensiveComputation2();
            cachedValue = val1 + val2; // 或者调换顺序  cachedValid = true;
            cachedValid = true;        // 或者调换顺序  cachedValue = val1 + val2;
            return cachedValue;
        }
    }
private:
    mutable std::atomic<bool> cacheValid{ false };
    mutable std::atomic<int> cachedValue;
}

如果使用std::atomic,那么对于上述代码的两条相互有关联的语句,不能约束他们的线程安全性。

5.2 要点

保证const成员的线程安全性,除非可以确信他们不会用在并发环境中。

对于单个要求同步的变量,使用std::atomic就足够了,相较于互斥量性能更好。

对于两个或更多变量或内存区域需要作为一整个单位进行操作时,就要动用互斥量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值