存档

作者存档

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

2019/06/19 5,621

今天在看 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,709

一. 引入

简单地说: 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 的 保活机制

继续阅读

使用 Windows API 设置 IE 代理

2019/05/06 5,933

设置 IE 代理有多种方法。常用的是修改注册表和调用 InternetSetOption API 。

1. 注册表

IE 设置在注册表中所在的位置是
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings
其中:

  • ProxyServer
    Value Type: REG_SZ
    Value Data: “ProxyServerName:Port”
  • ProxyEnable
    Value Type: REG_DWORD
    Value Data: 0 = Disable 1 = Enable
  • ProxyOverride 不使用代理的地址的列表
    Value Type: REG_SZ
    Value Data: “list_of_exclusion” 。可以使用通配符 * . 使用分号 ; 分隔, 如 192.168.1.*;*.domain.com;<local>

2. 使用 Windows API

示例代码如下 :

INTERNET_PER_CONN_OPTION_LIST List;
INTERNET_PER_CONN_OPTION Option[1];
unsigned long nSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
Option[0].dwOption = INTERNET_PER_CONN_FLAGS;
Option[0].Value.dwValue = PROXY_TYPE_DIRECT;
List.dwSize = nSize;
List.pszConnection = NULL;
List.dwOptionCount = 1;
List.dwOptionError = 0;
List.pOptions = Option;
BOOL setopts = InternetSetOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, nSize);

关于更多的信息请 参考这里 docs.microsoft.com

3.Others

a. 注意事项

在 IE9 之前, 以上步骤之后可能需要重启 IE 才能使设置生效。或者在打开 “Internet 属性” 面板时设置不生效。此时需要:

InternetSetOption(0, INTERNET_OPTION_SETTINGS_CHANGED, 0, 0);
InternetSetOption(0, INTERNET_OPTION_REFRESH, 0, 0);

来通知其它应用IE属性被修改。

b. 打开 “Internet 属性” 设置面板

Rundll32.exe shell32.dll,Control_RunDLL inetcpl.cpl,,4
Rundll32 可以做很多事情。具体可以看这里 List of Rundll32 Commands in Windows 10

从单例模式谈起(一)

2019/04/08 5,560

单例模式可能是大家最为熟知的一种设计模式,本身没什么好谈的。但是在 C++ 中,由单例模式可以引出一系列的问题,可能会比较有意思,这里探讨一下。

常见的简单实现

1.使用 static 实现

class S1 {
    public:
        static S1& GetInstance() {
            static S1 s;
            return s;
        }   
        void fun() {}

    private:
        S1(){cout<<"S1 inited"<<endl;}
        S1(const S1&) = delete;
        S1& operator=(const S1&) = delete;
};

class S2{ 
    public:
        static S2& GetInstance(){
            return s;
        }   
        void fun(){}
    private:
        S2(){cout<<"S2 inited"<<endl;}
        //...
    private:
        static S2 s;
};
S2 S2::s;

其中 S2 是要避免的。因为 1. 类的静态成员变量的初始化时间一般早于 main 函数 2. 如果静态成员的初始化里调用了其它类,可能出现未定义的错误。

2.使用指针判断是否初始化

class S3{
    public:
        static S3& GetInstance(){
            if(s == 0)
                s = new S3();
                 return *s;
        }

    private:
        S3(){}
        static S3 *s;
};

这两种方式在单线程程序中使用都是可以的,但如果在多线程中,就会出现问题。

Magic Static

对于 S1 要注意, C++ 局部静态变量的初始化可能不是线程安全的 , 这就是 Magic Static ,是指 返回一个静态局部变量的引用 的用法,在某些情况下,如S1 可能会被编译器这样解析:

继续阅读

TCP 的状态(二) : TCP 常见错误码

2019/04/01 8,306

TCP 协议错综复杂, 很容易出现错误,错误码非常之多。这里探究一些在编程中比较常见的错误码。tcp 错误码 在不同的操作系统中的值不同。这里取用 Linux 与 Windows 两种,如 connection_refused 在windows  中的值为10054, 在 Linux 中为54 .

eof :

  • 2 : End of file

eof 标志着流的结束。当对端关闭连接(调用了 shutdownclose) 后,处于 CLOSE_WAIT 状态。 本端会收到 FIN, ACK 。此时如果本端再试图 read ,则会读到 eof

connection_refused

  • 61 : Connection refused
  • 10061 : No connection could be made because the target machine actively refused it . 由于目标计算机积极拒绝,无法连接

在客户端试图与服务端建立连接的时候发生。一般是服务端没有处于监听状态。 客户端发送发 SYN , 但是收到了 RST, ACK

connection_reset

  • 54 : Connection reset by peer
  • 10054 : An existing connection was forcibly closed by the remote host . 远程主机强迫关闭了一个现有的连接。

对端对处于连接状态的socket 进行了异常断开, 如进程中断等。此时如果本端进行读操作,可能会得到此错误。 继续阅读

TCP 的状态(一): TCP有限状态机 (FSM)

2019/03/14 8,750

最近一段时间在解决一个网络方面的 BUG,发现自己对 TCP 的状态了解得不够,于是复习了一遍 TCP 的各种状态与状态间的转换,并做了一个整理,以加深自己的理解。

网络上的两台设备想要一起工作,就必须使用相同的网络协议。像 TCP 这种复杂的协议,我们很难简洁地描述其各种确切的操作。所以我们试图使用有限状态机来解释这个复杂的协议。

一. 有限状态机

有限状态机 (FSM : Finite State Machine) 又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。FSM 的四个基本概念如下:

  1. 状态 state: 描述机器在特定时间上处于的 环境(circumstance)或状况(status)
  2. 转换 Transition: 从一种状态到另一种状态的行为(act)
  3. 事件 Event: 导致状态发生变化的事情
  4. 动作 Action: 机器从一种状态转换之前对事件所做的响应

FSM通过解释协议可以处于的所有不同状态、可以在每个状态中发生的事件、针对事件采取的操作以及结果发生的转换来描述协议。协议通常在第一次运行时以特定的开始状态启动。尔后,它遵循一系列步骤使其进入常规操作状态,并根据特定类型的输入或其他情况移动到其他状态。状态机之所以称为有限状态机,是因为只有有限数量的状态。

二. TCP 的操作与状态

对于 TCP 来说,可以使用 FSM 来描述一个连接的生命周期 : 一个 TCP 设备和另一个 TCP 设备之间的每个连接,都从一个空状态(null state) 开始,经过一系列的状态变化,直到建立了(established)连接。然后它将保持这种状态,直到遇到某种事件,它将进行一系列状态直到回到关闭状态。
我们使用三个缩写词来表示状态间转换的三种类型消息,它们对应于 TCP 头的标志。(关于 TCP 头的内容可以参见我之前的博客 《TCP 协议概述》) :

继续阅读

跨平台工程构建:CMake 的使用(三) 引入库

2019/03/01 5,890

前一章介绍了 cmake 的基本语法 以及如何构建一个最简单的工程。这里接着聊一聊使用 cmake 构建工程的常用操作:添加库

一、由源代码添加库

这一节中我们将向工程中添加一个库项目。

假设我们需要开发一个 mathlib 库, 并在其他项目中调用,可以像下面这样操作:

 

1. 在工程路径下建立子文件夹 mathlib

2. 在 ./mathlib 中添加项目源文件 mymath.h , mymath.cxx,并添加 CMakeLists.txt:

继续阅读

跨平台工程构建:CMake 的使用 (二)基本语法

2019/02/20 8,326

组织结构

cmake 文件包括 CMakeLists.txt 和以 .cmake 为后缀的文件。
程序源文件最外层的 CMakeLists.txt 文件是 cmake 的入口文件,这个文件可以定义了整个工程的构建规范。它也可以使用 add_subdirectory() 命令来包含一个子文件夹,每个使用该命令添加的子文件夹中也需要有一个 CMakeLists.txt 文件。

变量

变量的包装与展开

在 cmake 中, 变量都是以字符串的形式存在的。使用 set( ) 包装变量, 使用 ${} 展开变量。${} 可以嵌套使用。 使用未包装的变量会导致空展开:

set(var_name_1 var1)
message("var_name is ${var_name_1}")

set(var_name_2 var2)
set(${var_name_2} var) #same as set(var2 var)
set(${${var_name_2}}_x foo) #same as set(var_x foo)
message("var2 is ${var2}")
message("var_name_2 is ${var_name_2}")
message("var_x is ${var_x}")
message("empty var is ${empty_var}")

执行结果如下:

var_name is var1
var2 is var
var_name_2 is var2
var_x is foo
empty var is

列表

继续阅读