求教个 C++ Get 函数怎么写的问题

94 天前
 Betsy

代码

#include<iostream>
#include <cstdint>
#include <unordered_map>

enum struct Status {
    kOk = 0,
};

struct Student {
    std::string name;
    std::size_t age;
};

class Table {
   public:
    Table() {
        this->map_.insert(std::make_pair("w1", Student("li", 23)));
        this->map_.insert(std::make_pair("s2", Student("zhao", 18)));
    }

    Status Get(const std::string& key, Student* value) {
        *value = this->map_[key];
        return Status::kOk;
    }

    Student Get(const std::string& key) { return this->map_[key]; }

   private:
    std::unordered_map<std::string, Student> map_;
};

int main(int argc, char* argv[]) {
    Table table;

    Student stu1;
    const Status& status = table.Get("w1", &stu1);
    std::cout << stu1.name << ":" << stu1.age << std::endl;

    const Student& stu2 = table.Get("s2");
    std::cout << stu2.name << ":" << stu2.age << std::endl;
    return 0;
}

结果

li:23
zhao:18

问题

1. Status Get(const std::string& key, Student* value);
2. Student Get(const std::string& key);

在 Java/Python 等语言中,个人更喜欢第 2 种写法;但是 C++ 中,一些项目更倾向于第 1 种写法,为啥呢?这样有什么好处吗?

2605 次点击
所在节点    C++
37 条回复
ipwx
93 天前
楼主上一个帖子里面也出现了类似的写法

const Status& status = table.Get("w1", &stu1);

这句话是错的。你应该

Status status = table.Get("w1", &stu1);

因为你真的返回的是临时对象啊,这句话执行完就没有了啊(
ipwx
93 天前
额其实第二句也是错的

const Student& stu2 = table.Get("s2");

它只能是

Student stu2 = table.Get("s2");

因为你在类里面

Student Get(const std::string& key) { return this->map_[key]; }

它返回的是 this->map_[key]; 的一个拷贝,而不是 this->map_[key]; 它本身的引用。
====


如果你要写成

const Student& stu2 = table.Get("s2");

你对应的类里面应该写成

const Student& Get(const std::string& key) { return this->map_[key]; }
===

楼主对于 C++ 对象的生存周期是完全不理解啊。。。
ipwx
93 天前
这样,楼主你把 C++ 的引用看成 “指针” 的语法糖就行了。

引用基本就是指针。。。

=====

所以你的第一种,一般可以写成(没有过编译器,手写,不保真):


const Student* Get(const std::string& key) {
auto it = this->map_.find(key);
return (it != this->map_.end()) ? &(it.second) : std::nullptr;
}


然后用的时候

auto myStudent = table.Get("w1");
if (myStudent) {
...
}
PTLin
93 天前
本质不就是错误处理,用写法二只能抛异常
Betsy
93 天前
@nevermoreluo 所以,你是建议使用 this->map_.at(key) 这样的写法吗?
Betsy
93 天前
@jones2000 我肯定是希望 class Table 释放的时候,map_ 中的 Student 也被释放的。如果按照你这种写法的话,首先我需要写一个析构函数,其次我需要在析构函数里面写 delete Student 的逻辑,感觉变得更加复杂了。
Betsy
93 天前
@lovelylain 这的确也是一种方法,但是最前面这个 const 会不会限制不住。

比如,在复杂逻辑下,会不会出现把 map 中的对象属性给修改掉的问题。

const Student* p = Get("key");
Student* q = const_cast<Student*>(p);
q.name = "ahahah";
Betsy
93 天前
@ipwx 返回指针,会不会存在跟 #27 一样的问题?
Betsy
93 天前
@PTLin 抛异常我觉得也可,在 Java 中会有大量抛异常然后处理异常的逻辑。但是在 C++ 的项目中,好像不怎么用异常处理。
jones2000
93 天前
@Betsy c++玩的就是指针,否则和 java,py 有什么区别。可以做内存池统一分配回收。 闲麻烦外面套一个智能指针 shared_ptr 。
Betsy
93 天前
@jones2000 但也不希望把 C++ 写成 C 呀。高级特性还是要用用的
lovelylain
92 天前
@ipwx 返回临时对象,用 const&接收返回值,符合 c++标准,没问题的,编译器会处理声明周期。
@Betsy #25 不清楚就多查文档,[]访问在不存在的时候会自动插入,at 不会自动插入,因为要返回引用,所以不存在就只能抛异常了,要想不自动插入不抛异常,就最好返回 const*。
@Betsy #27 代码是人写的,返回 const*表明了调用方不应该修改该对象,如果你非要较真可以去掉 const 后修改,那通过 table 地址还能获取到其私有 map 地址呢,代码 xjb 写出啥问题都有可能。如果你需要修改,可以明确定义一个返回无 const 的版本。
ipwx
92 天前
@lovelylain “ 返回临时对象,用 const&接收返回值,符合 c++标准,没问题的,编译器会处理声明周期。”

嘿你可真是个人才,还真有这个规范:

https://en.cppreference.com/w/cpp/language/reference_initialization

Lifetime of a temporary

Whenever a reference is bound to a temporary or to a subobject thereof, the lifetime of the temporary is extended to match the lifetime of the reference, with the following exceptions:

a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of the return expression. Such function always returns a dangling reference.
a temporary bound to a reference member in a constructor initializer list persists only until the constructor exits, not as long as the object exists. (note: such initialization is ill-formed as of DR 1696).
(until C++14)
a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function call: if the function returns a reference, which outlives the full expression, it becomes a dangling reference.
a temporary bound to a reference in the initializer used in a new-expression exists until the end of the full expression containing that new-expression, not as long as the initialized object. If the initialized object outlives the full expression, its reference member becomes a dangling reference.
a temporary bound to a reference in a reference element of an aggregate initialized using direct-initialization syntax (parentheses) as opposed to list-initialization syntax (braces) exists until the end of the full expression containing the initializer.

可是这规范又臭又长,而且 "with the following exceptions" 一不留神就用错。为啥不用肯定没问题、而且编译器会负责优化的返回值拷贝呢?
ipwx
92 天前
@lovelylain 简单来说,我反对任何形式地依赖这种语义的写法,原因如下:


const A& a = F();


这句话到底会不会产生一个 BUG ,依赖于 F() 的实现。如果 F() 不符合规范中的情形,你这种写法可能会出错。

对于一个工程而言,如果不能在调用方确定上述用法对不对,那就是个灾难。比如

template <typename F>
void someFunction(F&& f) {
const A& a = F();
...
}

当别人复用你的 someFunction 的时候,它就是个隐藏炸弹。

====


@Betsy 27 楼的问题,人家想做的时候照样能够先把 const T& cast 到 const T* 然后 const cast 。。。

想用是拦不住的。
lovelylain
92 天前
@ipwx 你可真是个嘴强王者,22 楼的回复暴露了你对 C++的一个错误认识,甭管这种用法是否值得推荐,它确实是可以的,而非你理解的是错误用法。不用回复我了,你嘴强你高兴就好。
ipwx
92 天前
@lovelylain 你确实 C++ 的细节比我掌握的好,你是个人才。

我只是不推荐楼主去陷入这种不适合工程开发的实践而已。
Hackerl
31 天前
std::optional<std::reference_wrapper<const Student>> Get(const std::string& key);

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1063354

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX