C++ 泛型编程之 SFINAE

什么是 SFINAE?

(Substitution Failure Is Not An Error) 匹配失败不是错误. 它可以从一组重载函数中剪裁掉不需要的模板实例.

SFINAE 是 C++ 模板的一个特性, 这个特性在 std::enable_if 中使用的非常广泛. 模板参数推导过程中, C++ 编译器会试图实例化一些候选的函数签名, 以确保函数调用时模板的精确匹配, 当找到一个不合适的匹配时(例如无效的参数或范围值), 则会将这个不合适的匹配从重载决议中删除, 而不是产生一个编译错误. 如果有且只有一个函数的话, 才会产生一个编译错误.

考虑下面的简单的乘法计数器

1
2
3
4
5
6
7
8
9
10
11
12
13
long multiply(int i, int j) { return i * j; }

template <class T>
typename T::multiplication_result multiply(T t1, T t2)
{
  return t1 * t2;
}

int main(void)
{
  multiply(4,5);
  return 0;
}

在 main 函数中的函数调用 multiply, 会导致编译器对模板参数的实例化, 虽然第一个非模板函数 multiply 明显是一个更佳的匹配. 在实例化的过程中, 会产生一个非法的类型 int::multiplication_result. 根据 SFINAE, 这个非法的实例化会被自动丢弃. 最终, 非模板的 multiply 会被调用. 编译通过.

Read on →

C++11 多线程, Std::future & Std::promise

C++11 中最让人高兴的新特性中线程库的支持一定榜上有名. C++11 中提供了 future 和 promise 来简化任务线程间的返回值操作; 同时为启动任务线程提供了 packaged_task 以方便操作.

std::packaged_task

模板类 std::packaged_task 可以包装任何可调用的对象(函数, lambda 表达式, std::bind, 或其他函数对象), 以便异步调用, 调用结果保存在 std::future 中, 可以通过成员函数 get_future 访问. 需要注意的是, std::packaged_task 是不可拷贝的(move only).

示例代码

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
#include <iostream>
#include <cmath>
#include <thread>
#include <future>
#include <functional>

void task_lambda()
{
    std::packaged_task<int(int,int)> task([](int a, int b) {
        return std::pow(a, b);
    });
    std::future<int> result = task.get_future();
    task(2, 9);
    std::cout << "task_lambda:\t" << result.get() << '\n';
}

void task_bind()
{
    std::packaged_task<int()> task(std::bind(std::pow, 2, 11));
    std::future<int> result = task.get_future();
    task();
    std::cout << "task_bind:\t" << result.get() << '\n';
}

void task_thread()
{
    std::packaged_task<int(int,int)> task(std::pow);
    std::future<int> result = task.get_future();
    std::thread task_td(std::move(task), 2, 10);
    task_td.join();
    std::cout << "task_thread:\t" << result.get() << '\n';
}

int main()
{
    task_lambda();
    task_bind();
    task_thread();
}
Read on →

C++14 std::optional

C++11已经发布将近2年时间, 各编译器也陆续有了较好的支持. C++14 也已经有相当多的提案被接受, 包括 std::optional, 泛型 lambda, make_unique, 动态数组等, 肯定会出现在下一个标准中. 关于新标准中的提案, 以及已经被接受的提案,详细信息可以参考, wikipedia:C++14, 以及 open-std 中的 C++ 标准草案.

“无意义”的值

函数并不总能返回有效的返回值, 很多时候函数可能返回”无意义”的值, 这不意味着函数执行失败, 而是表明函数正确执行了, 但结果却不是有用的值. 表示返回值无意义最常用的做法是增加一个”哨兵”的角色, 它位于解空间之外, 比如 NULL, -1, EOF, std::string::npos, vector::end()等. 但这些做法不够通用, 而且很多时候不存在解空间之外的”哨兵”.

std::optional

optional 库使用”容器”语义, 包装了”可能产生无效值”的对象, 实现了”未初始化”的概念. (在新标准未正式发布之前,可以参考 boost::optional 的实现.)

optional 使用”容器”语义, 为这种”无效值”的情形提供了一个较好的解决方案. 它很像一个仅能存放一个元素的容器, 它实现了”未初始化”的概念: 如果元素未初始化, 那么容器就是空的, 否则, 容器内就是有效的, 已经初始化的值.

Read on →

模板方法模式

意图

  1. 定义一个操作中的算法骨架, 将一些步骤延迟到子类中定义. 模板方法模式, 可以让子类不改变算法的结构重新定义算法的某些步骤.
  2. 基类声明算法的“占位符”, 子类来实现这个“占位符”.

讨论

组件设计者决定哪些算法的步骤是不变的? 哪些是会变化的? 不变的步骤在抽象类中直接实现, 可能变化的步骤给出默认的实现, 或者完全不实现. 变化的部分代表“hooks”, 或占位符. 可以在使用组件时, 在派生类中实现.

组件设计者设计算法的步骤, 步骤的顺序, 但同时允许用户来扩展或替换算法中的某些步骤.

模板方法模式在框架中的使用非常广泛. 每个框架都会实现框架的主要结构, 然后定义一系列的“占位符”给用户, 让用户有机会来自定义一些操作.这么做时, 框架就变成了核心部分, 用户自定义的操作就会变得容易. 这使得控制结构发生反转, 这个方式有一个很好的名字, “好莱坞原则”——“不要给我们打电话, 我们会打电话给你.”

Read on →

C++11 关键字 Using

这个特性, 10年前就已经有提案了, 直到 C++11 中才正式加入标准. ⊙﹏⊙b汗

使用小技巧来声明函数指针

在 c++03 中, 函数指针要这么写:

1
typedef void (*FunctionPtr)();

这个声明读起来相当的费劲, C 语言的初学者会很难理解上面的 typedef 表达的意思.

在 C++11 中, 我们可以使用更易读懂的方式来表达, 利用 using 关键字:

1
using FunctionPtr = void (*)();

如果你想去除丑陋的 * 号, 还可以利用类型特化这么来写:

1
2
#include <type_traits>
using FunctionPtr = std::add_pointer<void()>::type;

新关键字 using 当然不仅仅是为了这个简单的功能了, 最主要的目的是为了模板别名.

Read on →

[译] GotW #1 Solution: Variable Initialization – or Is It?

原文在这里 GotW #1 Solution: Variable Initialization – or Is It?

第一个问题是用来强调理解你写的代码的含义的重要性. 下面是几行简单的代码 – 大部分都与其他的有一些区别, 即使只是语法略有变化.

JG 问题

1. 下面的代码有什么不同?

1
2
3
4
5
6
7
8
9
10
11
12
13
widget w;                   // (a)

widget w();                 // (b)
widget w{};                 // (c)

widget w(x);                // (d)
widget w{x};                // (e)

widget w = x;               // (f)
widget w = {x};             // (g)

auto w = x;                 // (h)
auto w = widget{x};         // (i)

Guru 问题

2. 下面的每一行代码做了什么?

1
2
3
vector<int> v1( 10, 20 );   // (a)

vector<int> v2{ 10, 20 };   // (b)

3. 除了上面的情况以外, 使用 { } 初始化对象还有什么其他的好处?

4. 什么时候使用 ( ) 以及 { } 来初始化对象? 为什么?

Read on →

使用静态多态辅助动态多态

静态多态 (编译期多态) 与动态多态 (运行期多态)

关键字: 重载/模版和虚函数

类型: 编译期多态 (静态多态, 早绑定) 和运行期多态 (晚绑定) 编译期多态 (重载/模版), 运行期多态 (虚函数)

应用形式上: 静多态是发散式的, 让相同的实现代码应用于不同的场合. 动多态是收敛式的, 让不同的实现代码应用于相同的场合.

思维方式上: 静多态是泛型式编程风格, 它看重的是算法的普适性. 动多态是对象式编程风格, 它看重的是接口和实现的分离度.

std::shared_ptr 中的 deleter 是如何工作的?

标准库中的引用计数智能指针 shared_ptr 很有趣——你可以向其构造器传递一个函数或者仿函数 (function object, 或 functor), 当引用计数归零的时候, 它将在被引用对象上调用删除器 (deleter). 乍一看, 似乎没啥了不起啊, 但请看代码:

1
2
3
4
5
6
7
8
template<typename T>
class shared_ptr {
public:
  template<typename U, typename D>
  explicit shared_ptr(U* ptr, D deleter);
  //...

};
Read on →
C++

QtScript 与信号槽

Qt从4.3开始, 提供了 ECMAScript 支持,QtScript 模块提供了一些让 Qt 应用程序脚本化的类. 在 Qt4 中 Webkit 与 QtScript 使用了相同的 javascript 引擎实现.(Qt5 中, javascript 引擎使用了 google 的 V8)

在 QtScript 中使用信号槽

Qt Script 可以使用Qt的核心特性: 信号槽. 信号只能存在于 C++ 的代码中, 但槽函数,以及连接的动作可以放到 javascript 中来做

  1. C++ 调用 script: 连接 C++ 代码中的信号到 script 函数上. 这个 script 函数可以是 C++ 代码中包含的 script 字符串, 也可以是从文件中读进来的. 如果不想将 QObject 对象泄露到脚本的运行环境中时, 这个方法是非常有用的. 仅仅需要在 script 代码中定义信号需要怎么被响应, 剩下的就是把连接工作放到 C++ 代码里就可以了.
  2. Script 调用 C++: script 可以连接注入到脚本环境中的 C++ 对象的信号和槽, 在这种情况下, 槽函数还是定义在 C++ 代码中, 但是信号和槽的连接完全是动态的(在 script 中完成)
  3. 纯script: script 可以定义信号的响应函数句柄, 然后使用句柄建立信号与槽的连接. 比如: script 可以定义一个函数用来响应 QLineEdit::returnPressed() 信号, 然后连接信号与 script 函数.
Read on →

空基类优化

目的

优化空类数据成员的存储空间

别名

EBCO: Empty Base Class Optimization Empty Member Optimization

动机

大小为 0 的类在 C++ 中是不存在的. C++ 需要空类大小不为 0 以确保对象的标识. 例如下面的 EmptyClass 的大小就是非 0 的, 因为数组中每一个对象的标识都是唯一的. 如果 sizeof(EmptyClass) 的大小为 0, 指针算数就会失效. 一般情况下, 类似 EmptyClass 的类大小通常为 1.

1
2
class EmptyClass {};
EmptyClass arr[10]; // Size of this array can’t be zero.

当类似的类作为另一个类的数据成员时, 它的大小一般比 1 字节要大. 编译器通常 4 字节对齐来避免切割. 4 字节的空类对象只是占位符, 毫无用处. 避免浪费空间, 节省内存, 帮助对象更适应 CPU 缓存是非常有好处的.

Read on →

在 C++03 中实现强类型安全的枚举类型

意图

改进 C++ 枚举的类型安全性

动机

在 C++03 中枚举类型的类型安全性不够强, 有可能导致意想不到的错误. 虽然枚举是语言内置的特性, 但也可能由于编译器的不同, 而存在可移植性的问题. 枚举的问题基本上可以分为3类:

  • 隐式转换
  • 无法指定类型
  • 作用域问题

C++03 中枚举类型是部分类型安全的, 比如你将一个枚举类型的值直接赋值给另一个枚举类型, 而且无法从整形隐式转换到枚举类型. 但是, 最常见的枚举类型错误是: 它可以自动提升成整形. 例如下面的代码, 只有少数几个的编译器(比如 GNU 的 g++)会发出警告, 而 VC++ 不会有任何的提示.

1
2
3
4
enum color { red, green, blue };
enum shape { circle, square, triangle };
color c = red;
bool flag = (c >= triangle); // Unintended!
Read on →