IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++的构造tips -> 正文阅读

[C++知识库]C++的构造tips

作者:匿名用户
链接:https://www.zhihu.com/question/30196513/answer/563560938
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

C++难就难在:在C++中你找不到任何一件简单的事。上面有人把C++和物理作类比。我很同意。理论物理是一场无尽的旅程,总有最前沿的东西。我对神经科学很感兴趣,也有幸与一个神经科学相关专业的学生交流过,她还给我发过资料,我很感激。然而我在知乎上看到过一个相关的讨论。一个人说“我小时候就想知道大脑是如何工作的,于是我学了神经科学,如今我已经是神经科学博士,依然不知道大脑是如何工作的”。所以我的求知欲只能暂且到此为止。C++亦是如此。扯远了,我们来说C++有多难吧。我们只谈构造函数。假如我们有一个类Teacher。

class Teacher
{
private:
    std::string name;
    std::string position;
};

我们考虑给Teacher类加上构造函数。

class Teacher
{
private:
    std::string name;
    std::string position;

public:
    Teacher(const std::string& n, const std::string& p)
        : name(n), position(p) {}
};

虽然语义正确,但是如果我们的实参只为了传递给Teacher,传递之后而没有其他作用的话,那么这个实现是效率低下的。字符串的拷贝花销可观(关于std::string的COW,SSO,view的讨论是另一个故事了)。我们在C++11里面有右值引用和move语义,所以呢,我们可以改成这样。

class Teacher
{
private:
    std::string name;
    std::string position;

public:
    Teacher(const std::string& n, const std::string& p)
        : name(n), position(p) {}

    Teacher(std::string&& n, std::string&& p)
        : name(std::move(n)), position(std::move(p)) {};
};

你可能觉得这样也已经不错了。不过我们还有可能第一个参数右值,第二个参数左值。或者第一个参数左值,第二个参数右值。所以实际上我们需要四个函数的重载。

class Teacher
{
private:
    std::string name;
    std::string position;

public:
    Teacher(const std::string& n, const std::string& p)
        : name(n), position(p) {}

    Teacher(std::string&& n, std::string&& p)
        : name(std::move(n)), position(std::move(p)) {};

    Teacher(const std::string&& n, const std::string& p)
        : name(std::move(n)), position(p) {}

    Teacher(const std::string& n, const std::string&& p)
        : name(n), position(std::move(p)) {}
};

代码有点多。我们有没有什么方法写一个通用的函数来实现这四个函数呢?有。我们在C++11中有完美转发。

class Teacher
{
private:
    std::string name;
    std::string position;

public:
    template <typename S1, typename S2>
    Teacher(S1&& n, S2&& p)
        : name(std::forward<S1>(n)), position(std::forward<S2>(p)) {};
};

完成了。美滋滋。然而事情没有这么简单。如果我们的position有默认值,然后我们写如下代码的话。

class Teacher
{
private:
    std::string name;
    std::string position;

public:
    template <typename S1, typename S2 = std::string>
    Teacher(S1&& n, S2&& p = "lecturer")
        : name(std::forward<S1>(n)), position(std::forward<S2>(p)) {};
};


int main()
{
    Teacher t1 = { "david", "assistant" };
    Teacher t2{ t1 };
}

我们出现了编译期错误。因为Teacher t2{ t1 };的重载决议的最佳匹配是我们的模板,而不是默认的拷贝构造函数,因为拷贝构造函数要求t1是const的。所以,我们可能需要SFINAE和type traits来修改我们的代码。注意,默认函数参数不能类型推导,所以我们才需要的S2的默认模板参数。

class Teacher
{
private:
    std::string name;
    std::string position;

public:
    template <typename S1, typename S2 = std::string,
    typename = std::enable_if_t<!std::is_same_v<S1, Teacher>>>
    Teacher(S1&& n, S2&& p = "lecturer")
        : name(std::forward<S1>(n)), position(std::forward<S2>(p)) {};
}; 

仍然不对哦,因为我们的完美转发有引用折叠机制,我们应该判断的S1是Teacher&而不是Teacher。其次,如果有类继承我们的Teacher的话,拷贝的时候依然会出现这个问题,所以我们需要的不是is_same而是is_convertible。然而,如果我们直接写std::is_convertible_v<S1, Teacher>的话,我们实际上判定是不是可以转换的时候,还是会去看我们的构造函数。也就是说我们自己依赖了自己,无穷递归。所以我们需要的是std::is_convertible_v<S1, std::string>。所以,我们修改我们的代码。

class Teacher
{
private:
    std::string name;
    std::string position;

public:
    template <typename S1, typename S2 = std::string,
    typename = std::enable_if_t<std::is_convertible_v<S1, std::string>>>
    Teacher(S1&& n, S2&& p = "lecturer")
        : name(std::forward<S1>(n)), position(std::forward<S2>(p)) {};
};

其次,因为我们的默认参数是字面量,字面量是const char[]类型的。我们调用构造函数的时候,也会用字面量。字面量不是std::string类型会造成很多问题。然而在C++14中,我们可以用User-defined literals来把字面量声明成std::string类型的。不过记得命名空间,这个名字空间不在std中。我们这里不再讨论了。我们接下来讨论用构造函数初始化的问题。初始化有很多种写法,以下我列出有限的几种。

Teacher t1("david"s);
Teacher t2 = Teacher("david"s);

Teacher t3{ "lily"s };
Teacher t4 = { "lily"s };
Teacher t5 = Teacher{ "lily"s };

auto t6 = Teacher("david"s);
auto t7 = Teacher{ "lily"s };

我们用了auto。然而auto是decay的。而decltype(auto)不。所以,以下代码如果用auto的话可能不是你需要的。const Teacher& t8 = t1;
auto t10 = t8;我们需要写const auto&。此外,我们可以看出,用小括号和大括号好像没什么区别。不过,在一些情况下会有很大的差别。我们列出一些。

std::vector<int> vec1(30, 5);
std::vector<int> vec2{ 30, 5 };

甚至因为C++17的构造函数自动推导,我们可以写出更加疯狂的代码。std::vector vec3{ vec1.begin(), vec2.end() };这个代码是用初始化列表初始化的,也就是说我们得到的vec3中有两个iterator。好了,我们回过头来说auto。我们可以看到好像我们所有的初始化都可以用auto。是这样吗?如果我们写atomic的代码呢?auto x = std::atomic{ 10 };是可以的。因为在C++17中我们有Copy Elision。所以这里没有拷贝函数的调用,和直接定义并初始化是一致的。但是atomic初始化是有问题的。std::atomic x{};这样是不能零初始化的。当然了,这显然是API的不一致,或者说错误。所以我们有LWG issue 2334。预计在C++20修复这个问题。嘻嘻。以上内容基于《C++ templates》的作者Nicolai Josuttis的几场talk。最后不知道说什么,祝大家新年快乐。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-12-18 15:47:14  更:2021-12-18 15:49:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 14:06:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码