存档

文章标签 ‘c++11’

C++: 函数返回值可以重载吗

2024/03/03 1,953

函数重载(Function Overloading)允许在同一个作用域内定义多个功能类似但参数不同的函数。这些函数可以有相同的名字,但必须有不同的参数列表(参数的数量、类型或顺序不同)。编译器通过参数列表来决定调用哪个函数。

大部分语言并不支持返回值类型的重载。包括C++。

// OK
std::string to_string(int i);
std::string to_string(bool b);

std::string si = to_string(42);
std::string sb = to_string(true);

// ERROR
int from_string(std::string_view s);
bool from_string(std::string_view s);

int i = from_string("42");
bool b = from_string("false");

C++ 11 的一个新特性,使我们可以实现一个 接近 函数返回值重载的功能。即 [自定义转换函数][https://en.cppreference.com/w/cpp/language/cast_operator] 。来看下面的例子:

struct to_string_t
{
    std::string_view s;  //string_view from c++17

    operator int() const; 
    operator bool() const;
};

int i = to_string_t{"42"};  //  operator int() const; 
bool b = to_string_t{"true"};  //  operator bool() const;

需要注意,编译器需要知道转换的目标类型。如 auto i = to_string_t{"42"} ,编译器不知道应该调用哪个重载。
让我们回到开始:如何将 to_string_t 作为返回值,似乎就可以完成 函数返回值重载 的功能:

继续阅读

C++11: 返回值类型后置语法

2019/06/19 5,548

今天在看 cpprestsdk 的源码的时候发现一个方法的定义是这样的:

template<typename _Ty>
__declspec(noinline) auto create_task(_Ty _Param, task_options _TaskOptions = task_options())
    -> task<typename details::_TaskTypeFromParam<_Ty>::_Type>
{...}

语法为:

auto fun_name(argument-declarations...) -> return_type{}
我们知道 C++11 的 lambda 表达式可以使用 `->` 指定返回参数的类型, 没想到声明方法也可以这么用。这种写法和传统的方法有什么区别呢?

在泛型编程中,一个常见的例子如下:

template <typename T>
T add(T a, T b) {
  return a + b;
}

如果 `a` 和 `b` 的类型不同,需要怎么写呢?

template<typename R, typename T1, typename T2>
R add(T1 a, T2 b) {
  return a + b;
}

  auto ret_val = add<float>(1.0, 2);
  auto ret_val1 = add<decltype(1.0 + 2)>(1.0, 2);

我们在调用此方法时必须显示指定 `R` 的类型,  或者使用 `decltype` 运算符推导返回值类型。如果让调用的方式简单点呢?把返回值类型放到定义中去, 像这样:

template <typename T1, typename T2>
decltype(a + b) add(T1 a, T2 b) {
  return a + b;
}

然而这么写是编译不过的。因为 `a`, `b` 在参数列表中,编译器解析返回值的时候,它们还没有定义。那么我们可以这样写:

template <typename T1, typename T2>
decltype((*(T1*)0) + (*(T2*)0)) add(T1 a, T2 b) {
  return a + b;
}

不过这太晦涩了。

C++ 11 允许将返回值类型后置,前面使用 auto 占位:

继续阅读

关于 enable_shared_from_this

2019/05/16 6,655

一. 引入

简单地说: enable_shared_from_this 是为了解决 在类的内部获取自己的 shared_ptr 这件事情而存在的。

众所周知, 每一个对象都能通过this 指针来访问自己的地址。this 指针也是所有成员函数的隐含参数。然而有些时候,我们需要的不仅是 this,而是一个 “this的智能指针”。

这里有一个常见的场景:

class A {
public:
    A() :did_it_(false) {}
    ~A() {
        std::cout << "destoried" << std::endl;
    }

    void OnDo(bool did) {
        did_it_ = did;
        std::cout << "somthing did" << std::endl;
    }

    void DoSth_Async() {
        std::thread t([this]() {
            std::this_thread::sleep_for(std::chrono::seconds(5));
            //...do somthing
            OnDo(true);
        });
        t.detach();
    }
private:
    bool did_it_;
};

代码如上:在异步方法 DoSth_Async() 中调用了成员方法 OnDo(bool) . 这里存在一个问题: 当 OnDo() 被调用的时候,该类的是否还在生存中:

int main(){
    {
        std::shared_ptr<A> ptr(new A());
        ptr->DoSth_Async();
    }
    std::this_thread::sleep_for(std::chrono::seconds(5));
    return 0;
}

智能指针 ptr 在出作用域后立即被释放。所以当 OnDo() 被调用的时候,其所在的对象实际已经被释放了。如果确保在 OnDo() 被调用的时候,该对象仍然在生命周期内呢?一个方便的方法便上在构建线程的时候,将该对象的 shared_ptr 传入到线程。在该线程的生命周期内,该对象就会一直存在。这是一种利用 shared_ptr 的 保活机制

继续阅读

using 关键字在 C++ 中的几种用法

2018/03/13 18,135

对C++中 using关键字的几种用法的总结:

1. using 声明

using 声明 (using declaration) 是将命名空间中单个名字注入到当前作用域的机制,使得在当前作用域下访问另一个作用域下的成员时无需使用限定符 ::

// ...
{
    using std::map
    map<int, std::string> the_map; //ok
}
map<int, std::string> the_map2;  //error

using 声明将其它 namespace 的成员引入本命名空间的 当前作用域 (包括其嵌套作用域)  。一个 using 声明一次只引入一个命名空间成员,它使得无论程序中使用哪些名字,都非常准确。
利用 using 声明,可以改变派生类对父类成员的访问控制:

class Base{
protected:
    int bn1;
    int bn2;
};

class Derived: private Base{
public:
    using Base::bn1;
};

class DerivedAgain: public Derived{
};

int main(){
    Derived d;
    DerivedAgain da; 
    d.bn1 = 1;
    d.bn2 = 2; //error, 'bn2' is a private member of 'Base'
    da.bn1 = 3;  //ok
    std::cout<<d.bn1<<std::endl;

    return 0;
}

尽管 Derived 对 base 是私有继承,但通过 using 声明,我们还是可以在 Derived 中访问其成员,且后续的继承同样不受 private 限定的影响。

继续阅读

C++11/14 新特性 (多线程)

2017/03/28 8,952

在 C++11 之前 ,C++ 标准并没有提供统一的并发编程标准,也没有提供语言级别的支持。这导致我们在编写可移植的多线程程序时很不方便,往往需要面向不同的平台进行不同的实现,或者引入一些第三方平台,如Boost,pthread_win32 等。 从C++11开始 ,对并发编程进行了语言级别的支持,使用使用C++进行并发编程方便了很多。这里介绍C++11并发编程的相关特性。

1 线程

1.1 线程的创建

std::thread 的构造函数如下:

template< class Function, class... Args > 
explicit thread( Function&& f, Args&&... args );

我们只需要提供线程函数或函数对象,即可以创建线程,并可以同时指定线程函数的参数。

#include <iostream>
#include <thread>
using namespace std;

void func(const string& str){
    cout<<str<<endl;
}

class FuncCls{
public:
    void operator()(){
        cout<<"Funcls called"<<endl;
    }
};

int main(void){
    thread t1(func,"Hello World");
    FuncCls fcls;
    thread t2(fcls);

    if(t1.joinable()){
        t1.join();
    }
    t2.join();
    return 0;
}

join函数将会阻塞线程,直到线程函数执行完毕,主线程才会接着执行。如果线程函数有返回值,返回值将被忽略。 在使用线程对象的过程中,我们需要注意线程对象的生命周期。如果线程对象先于线程函数结束,那么将会出现不可预料的错误。可以通过线程阻塞的方式来等待线程函数执行完(join),或让线程在后台执行。 如果不希望线程被阻塞,可以调用线程的 detach() 函数,将线程与线程对象分离。但需要注意的是,detach 之后的线程无法再使用join来进行阻塞了,即detach之后的线程,我们无法控制了。当我们不确定一个线程是否可以join时,可以先使用 thead::joinable() 来进行判断。

另外,我们还可以通过 std::bind, lambda 来创建线程(其实就是使用函数对象创建线程)。

线程不可以被复制,但是可以被移动:
继续阅读

C++11/14 新特性(function/bind 可调用对象包装器与绑定器)

2017/02/28 8,141

1 可调用对象

在 C++ 中,可调用对象一般是指:

  • 一个函数指针
  • 一个重载 () 操作符的类对句(仿函数)
  • 一个可被转换为函数指针的类对象
  • 一个类成员函数指针
#include <iostream>

void func(void)
{
    std::cout<<"func(void) called"<<std::endl;
}

struct Foo
{
    void operator()(void)
    {
        std::cout<<"Foo::()(void) called"<<std::endl;
    }
};

struct Bar
{
    using ft_t = void(*)(void);
    static void func(void)
    {
        std::cout<<"Bar::func(void) called"<<std::endl;
    }

    operator ft_t(void)
    {
        return func;
    }
};

struct A
{
    int m_a;
    void mem_func(void)
    {
        std::cout<<"A:memfunc(void) called and m_a="<<m_a<<std::endl;
    }
};


int main() {
    //1.函数指针
    void(* func_ptr)(void) = &func;
    func_ptr();

    //2.仿函数
    Foo foo;
    foo();

    //3.可被转换为函数指针的类对象
    Bar bar;
    bar();

    //4.类成员函数指针或类成员指针
    void (A::*mem_func_ptr)(void) = &A::mem_func;
    int A::*mem_obj_ptr = &A::m_a;
    A aa;
    (aa.*mem_obj_ptr) = 123;
    (aa.*mem_func_ptr)();

    return 0;
}

上例中的这些对象(func_ptr,foo,bar,mem_func_ptr,mem_obj_ptr) 均可称之为 “可调用对象”。相应的,其类型可被称作“可调用类型”。注意这里只有成员函数有成员函数指针而没有函数类型或函数引用类型,这是因为函数类型并不能直接用于定义对象,而函数引用或以看做一个 const 的函数指针。 可调用对象具有比较统一的调用形式,即使用括号操作(除成员函数指针),而定义的方法各不一样。这样我们在试图使用统一的方式保存,或传递一个可调用对象时,会十分烦琐。

2 std::function 可调用对象包装器

std::function 是一个类模板,它可以容纳除了类成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟调用它们。
继续阅读

C++11/14 新特性 (使用 chrono 进行时间处理)

2017/01/03 7,512

在c++11以前,我们获取当前时间的时间戳,需要借助于第三方库,如 boost ,或者针对不同的操作做不同的处理:

#ifndef _MSC_VER
#include <sys/time.h>
#endif

long long GetTimeSpan()
{
    long long llTimeMs = 0L;
#ifdef _MSC_VER
    timeb tb;
    ftime(&tb);

    lTimeMs = tb.time * 1000 + tb.millitm;

#else
    timeval tmv;
    gettimeofday(&tmv,NULL);


    llTimeMs = (long long)(tmv.tv_sec) * 1000L + tmv.tv_usec / 1000;

#endif
    return llTimeMs;
}

而在c++11中,这个问题得到解决。 c++11 标准库中提供 chrono库,用来处理时间相关的数据。

1 duration 记录时长

duration 表示一段时间间隔。其原型:

template <class _Rep, class _Period>
class duration;

_Rep 为一个数值类型,如 int, long, 用来表示时钟数的类型。 _Period 为一个默认的模板参数 std::ratio,表示时钟周期。 ratio的原型:

template <intmax_t _Num, intmax_t _Den = 1>
class ratio

它表示一个时钟周期的秒数。_Num 代表分子,_Den代表分母,它们的比值就是一个时钟周期,可以通过调整分子与分母来表示任意一个时钟周期。如 ratio<1> 表示一个时钟周期为1秒,ratio<3600>表示一个时钟周期为1小时,ratio<1,1000>表示一个时钟周期为1毫秒,ratio<1,2>表示一个时钟周期为0.2秒,ratio<9/7>表示一个时钟周期为9/7秒。 标准库将一些常用的时钟周期做了定义:

typedef ratio<1LL, 1000000000000000000LL> atto;
typedef ratio<1LL,    1000000000000000LL> femto;
typedef ratio<1LL,       1000000000000LL> pico;
typedef ratio<1LL,          1000000000LL> nano;
typedef ratio<1LL,             1000000LL> micro;
typedef ratio<1LL,                1000LL> milli;
typedef ratio<1LL,                 100LL> centi;
typedef ratio<1LL,                  10LL> deci;
typedef ratio<                 10LL, 1LL> deca;
typedef ratio<                100LL, 1LL> hecto;
typedef ratio<               1000LL, 1LL> kilo;
typedef ratio<            1000000LL, 1LL> mega;
typedef ratio<         1000000000LL, 1LL> giga;
typedef ratio<      1000000000000LL, 1LL> tera;
typedef ratio<   1000000000000000LL, 1LL> peta;
typedef ratio<1000000000000000000LL, 1LL> exa;

typedef duration<long long,         nano> nanoseconds;
typedef duration<long long,        micro> microseconds;
typedef duration<long long,        milli> milliseconds;
typedef duration<long long              > seconds;
typedef duration<     long, ratio<  60> > minutes;
typedef duration<     long, ratio<3600> > hours;

在不同的时钟周期中,我们可以使用 chrono_cast 来进行转换。两个 duration还可以进行加减运算:

#include <chrono>
#include <iostream>
using namespace std;

int main(int argc, const char * argv[]) {

    chrono::hours hrs(2);   //两小时
    cout<<hrs.count()<<" hours = ";  //count() 函数获取时钟周期下的时钟数
    chrono::minutes mns = chrono::duration_cast<chrono::minutes>(hrs); //120分钟
    cout<<mns.count()<<" minutes"<<endl;

    chrono::minutes tm = hrs - chrono::minutes(30);  //时长的加减运算
    cout<<hrs.count()<<" hours - 30 minutes = " <<tm.count()<<" minutes = ";

    typedef chrono::duration<double,ratio<3600,1>> hs;  //时长的类型转换。注意 duration 的 _Rep 参数类型。
    hs c = chrono::duration_cast<hs>(tm);
    cout<<c.count()<<" hours"<<endl;


    return 0;
}

//#  输出 #:
// 2 hours = 120 minutes
// 2 hours - 30 minutes = 90 minutes = 1.5 hours

继续阅读

C++11/14 新特性(for循环)

2016/12/05 8,587

在C++ 中,遍历一个容器的方法一般是这样的:

#include <iostream>
#include <vector>
int main(void)
{
    std::vector<int> vec;
    //do sth
    std::vector<int>::iterator it = vec.begin();
    for(; it != vec.end(); ++it)
    {
        std::cout<< *it <<std::endl;
    }
    return 0;
}

对STL比较熟悉的程序员肯定还知道在 <algorithm> 中有一个 for_each 算法,可以用来完成上述功能:

int main(void)
{
    std::vector<int> vec;
    //do sth
    std::for_each(vec.begin(), vec.end(), [](auto val){std::cout<<val<<std::endl;});
    return 0;
}

这里借助了 auto 关键字和 lambda 表达式简化了操作。 std::for_each 比起前面 for 循环,最大的好处是不再需要关注迭代器的概念,而只需要关心容器中的元素类型即可。 但这两种方法,都必须显式的给出容器有开头和结尾(begin,end).这是因为上面的两种方法都不是基于范围(range)来设计的. 范围的概念在很多高级语言中都有涉及。例如下面这段 python 代码:

arr = [1,2,3,4,5]
for n in arr:
    print n

在这种循环中,不再需要关心容器的两端,循环会自动以容器的范围进行展开。而且这种语法可以清楚地表明它的意义。使用这种方式进行循环无疑会使编码和维护更加简便。 现在,c++11 可有了基于范围的 for 循环

继续阅读