从reddit/hackernews/lobsters/meetingcpp/purecpp知乎/等等摘抄一些c++动态
周刊项目地址|在线地址 |知乎专栏 | 腾讯云+社区 |
欢迎投稿,推荐或自荐文章/软件/资源等
可以贴在下一期草稿里 草稿链接
2022 1008
标准委员会动态/ide/编译器信息放在这里
编译器信息最新动态推荐关注hellogcc公众号 本周更新 2022-10-05 第170期
直接贴个代码 https://godbolt.org/z/Yxc58vrWW
#include <cassert>
#include <coroutine>
#include <sstream>
#include <string_view>
#include <optional>
struct task {
struct promise_type {
task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
template<class T> void return_value(T) { }
void unhandled_exception() {}
};
};
template<class T>
class awaiter {
public:
auto operator co_await() {
struct {
awaiter& aw;
auto await_ready() const noexcept -> bool { return static_cast<bool>(aw.value); }
auto await_suspend(std::coroutine_handle<> coroutine) noexcept {
aw.coroutine = coroutine;
return true;
}
auto await_resume() const noexcept {
struct reset {
std::optional<T>& value;
~reset() { value = {}; }
} _{aw.value};
return *aw.value - '0';
}
} awaiter{*this};
return awaiter;
}
void process(const T& value) {
this->value = value;
coroutine.resume();
}
private:
std::optional<T> value{};
std::coroutine_handle<> coroutine{};
};
class parser {
public:
explicit parser(std::stringstream& out) : out{out} { }
void parse(std::string_view in) {
for (const auto value : in) {
input.process(value);
}
}
private:
task parse_impl() {
enum state { A, B, C } state;
for (;;) {
const auto value = co_await input;
const auto in = value != 0;
switch (state) {
case A:
out << '0';
state = in ? B : A;
break;
case B:
out << '0';
state = in ? C : A;
break;
case C:
out << (in ? '0' : '1');
state = in ? C : A;
break;
};
}
}
awaiter<char> input{};
task start{parse_impl()};
std::stringstream& out;
};
int main() {
{
std::stringstream out{};
parser p{out};
p.parse("0");
assert("0" == out.str());
}
{
std::stringstream out{};
parser p{out};
p.parse("01");
assert("00" == out.str());
}
{
std::stringstream out{};
parser p{out};
p.parse("0110");
assert("0001" == out.str());
}
{
std::stringstream out{};
parser p{out};
p.parse("0001");
assert("0000" == out.str());
}
{
std::stringstream out{};
parser p{out};
p.parse("000110");
assert("000001" == out.str());
}
{
std::stringstream out{};
parser p{out};
p.parse("0110100010010001101001000111110010011001");
assert("0001000000000000010000000000001000000100" == out.str());
}
}
cppfront的设计。看个乐。草木大哥上次玩的constraint也探索了好几年。这个玩意我估计也得玩几年。不会进。只是提供个思路
众所周知,range for里的临时变量左值有问题
class Keeper {
std::vector<int> data{2, 3, 4};
public:
~Keeper() { std::cout << "dtor\n"; }
// Returns by reference
auto& items() { return data; }
};
// Returns by value
Keeper GetKeeper() {
return {};
}
void Use() {
// ① Use the result of GetKeeper and return
// over items
for(auto& item : GetKeeper().items()) {
std::cout << item << '\n';
}
}
这个遍历很有可能挂掉。UB。但是我非要这么写,keeper类怎么设计呢?
class Keeper {
std::vector<int> data{2, 3, 4};
public:
~Keeper() { std::cout << "dtor\n"; }
auto& items() & { return data; }
// ④ For rvalues, by value with move
auto items() && { return std::move(data); }
};
注意这两个items后面的&限定,两种限定约定了被调用的时候走左还是右值,如果是range for循环,就调用第二个items,救一下data的生命,就没问题了。
我觉得还是尽量别range for里乱搞。容易误用。有的类设计类这种方法,如果有的类没这么设计,不就完了。
周所周知,临时变量的生命周期是一行,来一个复杂的例子
考虑一行上锁自动解锁
template<typename> struct LockableData;
namespace std
{
template<typename Data>
struct default_delete<LockableData<Data>>
{
void operator()(LockableData<Data>* p)
const noexcept { p->m.unlock(); }
};
}
template<typename Lockable>
struct [[nodiscard]] LockedData
{
LockedData(Lockable* l = nullptr) : l(l)
{ if (l) l->m.lock(); }
auto operator->() const noexcept
{ return std::addressof(l->data); }
private:
std::unique_ptr<Lockable> l;
};
template<typename Data>
struct LockableData
{
LockedData<LockableData> Lock() { return this; }
private:
friend struct LockedData<LockableData>;
friend struct std::default_delete<LockableData>;
std::mutex m;
Data data;
};
使用例子
struct WidgetInfo
{
std::string name;
int times_toggled = 0;
};
class Widget
{
LockableData<WidgetInfo> info;
public:
void SetName(std::string name)
{
auto lock = info.Lock();
lock->name = name;
lock->times_toggled = 0;
}
std::string GetName()
{
auto lock = info.Lock();
return lock->name;
}
void Toggle()
{
{ // scope the lock
auto lock = info.Lock();
lock->times_toggled++;
}
FlipSwitchesRandomly();
}
};
目前来看还是没啥问题,但是要多一个lock,很自然的,你想到了省略这一行
template<typename Data>
struct LockableData
{
LockedData<LockableData> Lock() { return this; }
auto operator->() { return Lock(); } // NEW!
private:
friend struct LockedData<LockableData>;
friend struct std::default_delete<LockableData>;
std::mutex m;
Data data;
};
class Widget
{
LockableData<WidgetInfo> info;
public:
void SetName(std::string name)
{
auto lock = info.Lock();
lock->name = name;
lock->times_toggled = 0;
}
std::string GetName()
{
return info->name; // lock-read-unlock
}
void Toggle()
{
info->times_toggled++; // lock-modify-unlock
FlipSwitchesRandomly();
}
};
问题来了。info->调用生成了一个临时对象,临时对象这一行结束就释放了,可能会出现读的不一样的问题,但这问题不大,真正的问题是这种用法可能导致锁两次
比如上面这个toggle,伪代码
// Evaluate right hand side
LockedData<WidgetInfo> lock1 = info.operator->();
int rhs = std::max(lock1->times_toggled, 10);
// Evaluate left hand side
LockedData<WidgetInfo> lock2 = info.operator->();
// Perform the assignment
lock2->times_toggled = rhs;
// Destruct temporaries in reverse order of construction
destruct lock2;
destruct rhs;
destruct lock1;
明显锁了两次。可能->这个方法过于有问题,我直接调用,比如
std::string GetName()
{
return info.Lock()->name;
}
应该不会有问题了吧, 如果toggle这么实现
void Toggle()
{
// suspicious double-lock - more likely to be spotted in code review
info.Lock()->times_toggled = std::max(info.Lock()->times_toggled, 10);
FlipSwitchesRandomly();
}
也是有同样问题的
RAII的烦恼也很多啊。解决方法可能是out_ptr或者std::synchronized_value folly::synchronize这种类似的玩意。别自己写了。可能想不到
不太懂
分析了一波,是编译器bug。msvc 16.10 以下的版本有问题,修复记录 https://devblogs.microsoft.com/cppblog/cpp20-coroutine-improvements-in-visual-studio-2019-version-16-11/
用concept实现crtp。之前也介绍过类似的
// we create a concept can_work to check if do_work is implemented
// this will describe our interface
template <typename T>
concept can_work = requires(T t) {
t.do_work();
};
// now we apply this concept to an empty type which represents a worker (or our base class)
template<can_work T>
struct worker : public T {};
// now create a concrete worker (corresponding derived) where we implement do_work
struct concrete_worker {
void do_work() {
// ...
}
};
// nice to have: an alias for our concrete worker
using my_worker = worker<concrete_worker>;
//...
// which we can use now
my_worker w;
w.do_work();
面向接口的感觉
没啥说的。c++23就能用了。之前你可以用absl的或者boost的。都差不多
一个COW vector大概的样子
template <class T>
class CowVector {
struct State {
std::atomic<int> ref;
size_t size;
size_t capacity;
T elements[];
}
State* state;
// if we're not unique, we need to allocate
// a new State and copy the elements.
// if we are unique, this is a no-op.
void copy_on_write();
public:
// copy constructor *never* allocates.
// just increments ref-count
CowVector(CowVector const& rhs)
: state(rhs.state)
{
++state->ref;
}
// and the mutable and const accessors do different things
auto operator[](size_t idx) -> T& {
copy_on_write();
return state->elements[idx];
}
auto operator[](size_t idx) const -> T const& {
return state->elements[idx];
}
};
怎么更干净更灵活的copy_on_write? 这套代码怎么用 Deducing this 改写
template <class T>
class CowVector {
public:
auto operator[](this CowVector& self, size_t idx) -> T&;
auto operator[](this CowVector const& self, size_t idx) -> T const&;
};
Self应该模版化
template <class T>
class CowVector {
struct State { ... };
State* state;
// this one (potentially) copies
auto get_state() -> State*;
// this one doesn't, because const
auto get_state() const -> State const* { return state; }
public:
template <class Self>
auto operator[](this Self& self, size_t idx)
-> std::copy_const_t<Self, T>&
{
return self.get_state()->elements[idx];
}
};
里面还讨论了很多边角场景,感兴趣的可以看看
一个map存数据,如果存在就不插入
object* retrieve_or_create(int id)
{
static std::unordered_map<int, std::unique_ptr<object>> m;
// see if the object is already in the map
auto [it,b] = m.emplace(id, nullptr);
// create it otherwise
if(b) it->second = std::make_unique<object>(id);
return it->second.get();
}
很常规。问题在于object可能非常大,可能构造异常。try catch一下,正好有try_emplace
这个接口
object* retrieve_or_create(int id)
{
static std::unordered_map<int, std::unique_ptr<object>> m;
auto [it,b] = m.try_emplace(id, std::make_unique<object>(id));
return it->second.get();
}
但是问题并没有解决,我们希望的是,直到需要调用make的时候,再调用。推迟到emplace 那一刻
template<typename F>
struct deferred_call
{
using result_type=decltype(std::declval<const F>()());
operator result_type() const { return f(); }
F f;
};
object* retrieve_or_create(int id)
{
static std::unordered_map<int, std::unique_ptr<object>> m;
auto [it,b] = m.try_emplace(
id,
deferred_call([&]{ return std::make_unique<object>(id); }));
return it->second.get();
}
针对string char*转换问题,加个补丁
template<typename F>
struct deferred_call
{
using result_type=decltype(std::declval<const F>()());
operator result_type() const { return f(); }
// "silent" conversion operator marked with ~explicit
// (not actual C++)
template<typename T>
requires (std::is_constructible_v<T, result_type>)
~explicit constexpr operator T() const { return {f()}; }
F f;
};
decltype(auto)
可以拿到真正的类型 auto有时候拿不到引用类型
划水严重,保佑不被开。