查看: 117|回复: 0

C++ 中文周刊 第83期

[复制链接]

2

主题

2

帖子

6

积分

新手上路

Rank: 1

积分
6
发表于 2022-11-30 13:20:00 | 显示全部楼层 |阅读模式
从reddit/hackernews/lobsters/meetingcpp/purecpp知乎/等等摘抄一些c++动态
周刊项目地址|在线地址 |知乎专栏 |腾讯云+社区
弄了个qq频道,手机qq点击进入
欢迎投稿,推荐或自荐文章/软件/资源等
可以贴在下一期草稿里 草稿链接
2022 1008
<hr/>资讯

标准委员会动态/ide/编译器信息放在这里
编译器信息最新动态推荐关注hellogcc公众号 本周更新 2022-10-05 第170期
文章


  • Did you know that C++20 introduced coroutines? (co__await)
直接贴个代码 https://godbolt.org/z/Yxc58vrWW

  • Cpp2 design notes: UFCS, “const”, “unsafe”, and (yes) ABI
cppfront的设计。看个乐。草木大哥上次玩的constraint也探索了好几年。这个玩意我估计也得玩几年。不会进。只是提供个思路

  • The Power of Ref-qualifiers
众所周知,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里乱搞。容易误用。有的类设计类这种方法,如果有的类没这么设计,不就完了。

  • The gotcha of the C++ temporaries that don’t destruct as eagerly as you thought
周所周知,临时变量的生命周期是一行,来一个复杂的例子
考虑一行上锁自动解锁
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这种类似的玩意。别自己写了。可能想不到

  • Feel free to stop using IMultiLanguage2::DetectInputCodepage
不太懂

  • Debugging coroutine handles: Looking for the source of a one-byte memory corruption
分析了一波,是编译器bug。msvc 16.10 以下的版本有问题,修复记录 https://devblogs.microsoft.com/cppblog/cpp20-coroutine-improvements-in-visual-studio-2019-version-16-11/

  • Static, Dynamic Polymorphism, CRTP and C++20’s Concepts
用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: flat_map, flat_set, et al.
没啥说的。c++23就能用了。之前你可以用absl的或者boost的。都差不多

  • Copy-on-write with Deducing this
一个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];
    }
};
里面还讨论了很多边角场景,感兴趣的可以看看

  • Deferred argument evaluation
一个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;
};
视频


  • C++ Weekly - Ep 344 - decltype(auto): An Overview of How, Why and Where
decltype(auto)可以拿到真正的类型 auto有时候拿不到引用类型
开源项目需要人手


  • asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群384042845和作者对线
  • pika 一个nosql 存储, redis over rocksdb,非常需要人贡献代码胖友们, 感兴趣的欢迎加群294254078前来对线
新项目介绍/版本更新


  • reflecxx 用libclang实现静态反射
工作招聘

划水严重,保佑不被开。
<hr/>看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢!  你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持!
本文永久链接
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表