SFINAE(Substitution failure is not an error) 被用在很多模板的花式操作里,笔者使用的时候多半是为了将运行时的多态替换成编译器的静态多态。 简单版:检测一个类T是否存在某名字的成员函数 举个例子,有如下的类定义: 希望能有一个静态的检测,能够实现 那么可以这样[1]: 基本逻辑是如果被测试的typename T有成员函数,那么调用HasSomeWhatFunction的静态方法test<T>()时,模板替换是按照返回值类型为one(char)来生效的;反之如果不存在SomeWhatFunction的函数,那么返回类型one的模板替换失败,继续查找,匹配到万能重载(catch-all overload) 的static two test(…)里。 这种做法的可读性在模板的骚操作里算是好的,但是它无法区分SomeWhatFunction的函数签名(参数类型、返回值类型)。 普通版:检测一个类T是否存在某签名的成员函数 接上个例子拓展一下: 这个就需要更复杂一些的逻辑了: 可以看到这个实现比之前的多定义了一个内部类Check。在初始化enum value值时会调用test,然后会尝试模板替换,进而想要去特化struct Check。如果失败了(不存在对应签名的函数),那么会回滚到static two test里。 这个实现其实能够覆盖许多场景了,但是还有一个奇怪的需求满足不了:如果要判断一个类的模板成员函数呢? 地狱版:检测一个类T是否存在某签名的模板成员函数 小改一下SomeWhatClass 如果需要检测void SomeTempMemberFunc<int, T&>是否存在,咋办? 搜了一圈,解释的最清楚的是参考[2]里面描述的,利用std::declval的解法。 几个知识点: decltype里利用了含有逗号运算符[3]的表达式; declval可以避开构造函数而使用类成员; 参考 [1] writing and using a C++ template to check for a function’s existence http://www.cplusplus.com/forum/beginner/70134/ [2] SFINAE Hell: detecting… Continue reading C++ SFINAE 检测是否存在某个名称的成员函数
Tag: c++11
std::future 与 std::promise 简单使用、简单原理
C++11有了一些关于线程的模型,在此之前C++里可是各自为政的,各种线程库各种神奇用法。其中有两个好玩的东西就是std::promise<T>和std::future<T>,下文书中形容它们像是个“虫洞”。 std::future是虫洞的出口:一个未来对象的“获取器”,在未来的某一刻它能返回一个有用的值,但现在还没有… std::promise是虫洞的入口:我们要保证在未来的某一刻给一个值进去,但不是现在… Basic usage 一般来说这俩是成对使用的,看个代码: #include <cassert> #include <chrono> #include <future> #include <iostream> #include <string> #include <thread> using namespace std::chrono_literals; int main() { std::promise<int> p1, p2; std::future<int> f1 = p1.get_future(); std::future<int> f2 = p2.get_future(); p1.set_value(42); assert(f1.get() == 42); std::thread t([&]() { std::this_thread::sleep_for(100ms); p2.set_value(43); }); auto start_time = std::chrono::system_clock::now(); assert(f2.get() == 43); std::chrono::duration<double, std::milli> elapsed… Continue reading std::future 与 std::promise 简单使用、简单原理
std::unique_ptr 关于智能指针的种种
这个域名也是突发奇想买到的,作为unique-ptr.com,为了对得起这个名儿,那就说一下智能指针算了。 前传。裸指针和auto_ptr 裸指针就是高度自治(要自己管)的指针,在符合基本法(误)的前提下,使用裸指针的人可以为所欲为。裸指针的一个问题是,它有点不符合现代C++内存管理的指导思想,使用者必须对它的生命周期负责,否则有可能发生: 内存泄漏(memory leak):如果new一大片内存但是由于代码复杂到头来忘了释放,就有可能耗尽内存资源;内存泄漏还有一种情况:如果申请内存做事情做了一半抛出了异常——代码跑到了exception的花括号里之后,原来申请的内存直接没办法拿到并且释放; 悬挂指针(dangling reference):指针已经被删除了,但其内存仍然在使用中;另外还有双重删除(double delete)的问题,当程序的某部分要删除一个已经被删除的指针时,即可出现这种情况;还有一种,指针所指的内存已经失效了(比如出了作用域了),但是后面的代码还用着指针; 应该是为了缓解这类问题,C++ 98标准搞出来了个智能指针:auto_ptr。相当于加了一个“管家”,在对象的内部保存那个裸指针,在管家析构的时候自动释放裸指针的内容,如果赋值的话那就把裸指针的所有权转交给新的管家。但是由于移动(move)语义是C++11才有的,auto_ptr在实现的时候,如果在两个智能指针对象之间作赋值(operator=)或者拷贝构造,它的参数都是非const类型的。为啥呢?因为它是会调用original_auto_ptr.release()方法拿到原始裸指针。这个有点无奈的操作是为了实现指针所指对象的“占有权”管理:两个auto_ptr不允许管理同一个目标裸指针。在“拷贝”的时候,相当于做了“主权移交”。 Copying an auto_ptr copies the pointer and transfers ownership to thedestination: both copy construction and copy assignment of auto_ptrmodify their right hand arguments, and the “copy” is not equal to theoriginal.— https://en.cppreference.com/w/cpp/memory/auto_ptr 这种操作并不符合常人对“拷贝“的理解,更像是”剪切“操作。且正是因为这个问题,切不可把auto_ptr放到stl容器里面,否则会得到各种编译错误。典型案例是这个:https://stackoverflow.com/questions/111478/why-is-it-wrong-to-use-stdauto-ptr-with-standard-containers另外由于析构的时候是用delete,auto_ptr里也不能放指针数组(那种需要用delete[]). unique_ptr 不同于auto_ptr,unique_ptr 的赋值运算符只接受典型地由 std::move 生成的右值。刚才说了,“右值”是C++11移动语义的产物,它比较好地解决了主权移交时的尴尬问题。这怎么说呢,一个智能指针的命运啊,当然要靠自我奋斗,但是也要考虑到历史的行程。回顾看之前说的裸指针的两个问题: 内存泄漏:由于析构函数里会delete管理的裸指针对象,所以在跑出对象作用域的时候,编译器会帮我们解决好问题,不用操心了; 悬挂指针:缓解,但不能完全避免。 一个例子是 #include <iostream> #include <memory>… Continue reading std::unique_ptr 关于智能指针的种种