【问题标题】:C++ : rebind variable of generic typeC ++:重新绑定泛型类型的变量
【发布时间】:2021-08-04 16:48:48
【问题描述】:

通过重新绑定变量为泛型类型重用堆内存。

我想做的事:

template <typename T> //unconstrained type
//...
{
  //in some complex procedural logic
  T rebindable = .../*r-value*/;

  //...
  //some procedure dependent case:
  T rebindable = .../*different r-value*/; //rebind (I am aware this does not work)
}

为什么这是一个值得一问的问题:

许多过程算法在其范围内维护并持续对内存进行操作。 有时,完全替换其中一个值在逻辑上是必要的,在编程上这意味着解构和构造,可能是不确定的次数(重新绑定)。

通常,这可以通过利用迭代来克服,例如在可能无法分配的数字类型上实现 smart-bisection root finder

但是,一些更复杂的过程算法可能无法在功能上轻松编写,尤其是在重新绑定由过程状态决定的情况下。

因此,以编程方式实现重新绑定对于通用算法可能非常有用。

不满意的答案

(这绝不是对任何人或其答案的攻击)

Unique_ptr:

template <typename T> //still unconstrained (which is a plus)
//...
{
  auto ptr = std::unique_ptr<T>{.../*dangling reference (T*)*/};

  //...
  ptr = std::unique_ptr<T>{.../*different dangling reference*/};
}

这样解决了不限制类型,但是使用的内存不是堆内存的问题。

作业:

template <typename T>
  requires std::assignable_from<T, T> //type constraint!
//...
{
  T assignable = .../*r-value*/;

  //...
  assignable = .../*different r-value*/;
}

虽然这是在堆上,但它是以限制类型为代价的。

新展示位置:

template <std::copy_constructible T> //type constraint!
//...
{
  T assignable = .../*r-value*/;

  //...
  assignable.~T();
  new (&assignable) T {.../*different r-value*/};
}

通过重新解构和重新构造赋值 new 值被反弹,但是这仍然有一个关联的类型约束。

【问题讨论】:

  • @mark_s 请停止编辑我的问题以满足您的个人格式要求。

标签: c++ generics memory-management binding value-type


【解决方案1】:

如果您愿意为堆栈上的额外布尔值付费,像 std::optional 这样的类型可以解决问题。

template <typename T> //unconstrained type
void func()
{
    //in some complex procedural logic
    std::optional<T> rebindable;
    rebindable.emplace(/*r-value*/);

    //...
    //some procedure dependent case:
    rebindable.reset();
    rebindable.emplace(/*different r-value*/);
}

我认为你需要它可以移动构造,因为你必须通过 emplace 函数。

【讨论】:

  • 我觉得作为一个想法,它更干净。如果我必须这样做,我可能会使用 rebindable&lt;T&gt; 类来执行此操作。
【解决方案2】:

使用新位置重新绑定

最后一个使用placement new的例子不需要相关的类型约束!

表达式T t = r-value;不调用构造函数,同理,表达式new (&amp;t) T {r-value};也不调用构造函数。

placement new 绕过带有 r 值的构造函数

这里是这种行为的一个例子:(在 GCC 上编译)

#include<iostream>
#include<utility>

struct C
{
    int n;
    static C make_new(int n) {return C{n};}
    ~C() = default;
    C(int n): n(n) {}
    C(const C &) = delete;
    C(C&&) = delete;
};

template <typename T>
void inline rebind(T& re_bin_ref, T&& val) {
    re_bin_ref.~T();
    new (&re_bin_ref) T {val};
}

template <typename T, typename... TArgs>
void inline emplace_rebind(T& re_bin_ref, TArgs&&... args) {
    re_bin_ref.~T();
    new (&re_bin_ref) T {std::forward<decltype(args)>(args)...};
}

template <typename T>
void inline procedural_rebind(T& re_bin_ref, auto f) {
    re_bin_ref.~T();
    new (&re_bin_ref) T { f() };
}

int main()
{
    
    std::cout << "\nREBINDING:\n";
    C re_bin = C::make_new(0);
    std::cout << "init: " << re_bin.n << std::endl;
    
    //...
    
    re_bin.~C();
    new (&re_bin) C {C{1}};
    std::cout << "pr-value: " << re_bin.n << std::endl;
    
    //...
    
    //re_bin.~C();
    //new (&re_bin) C {std::move<C>(C::make_new(2))}; //requires C&&
    //std::cout << "x-value: " << re_bin.n << std::endl;
    
    //...
    
    //rebind(re_bin, C::make_new(3)); //requires C&&
    //std::cout << "explicit rebind: " << re_bin.n << std::endl;

    //...
    
    emplace_rebind(re_bin, 4);
    std::cout << "explicit emplace rebind: " << re_bin.n << std::endl;

    //...

    procedural_rebind(re_bin, [](){return C{5};});
    std::cout << "explicit pr-value procedural rebind: " << re_bin.n << std::endl;
    
    return 0;
}

GodBolt 上的完整文件: https://godbolt.org/z/cz7Y6qzbe

【讨论】:

  • re_bin.~C(); 我认为 UB 从这里开始 - 调用自动(在堆栈上)变量的析构函数 - 这仅在 C re_bin 由放置新分配时才有效,请参阅 en.cppreference.com/w/cpp/language/destructor “...请注意,直接为普通对象(例如局部变量)调用析构函数会在作用域结束时再次调用析构函数时调用未定义的行为...。”我认为第一次分配必须是新的展示位置。
  • T t = r-value; does not call the constructor,我不确定你的意思;这样的代码通常会调用构造函数(移动构造函数)。
  • @RichardCritten 如果析构函数超出范围时在新对象在同一空间中开始其生命周期之前被调用,则只有UB。如果在放置 new 发生之前抛出异常,它将是 UB
  • 如果你使用rebind函数,你仍然应用一个约束,你“重新绑定”到的prvalue必须是noexcept,并且移动构造函数noexcept。你也有T不包含引用或const限定数据成员的约束,否则你必须每次都通过*std::launder(std::addressof(re_bin))访问re_bin(或绑定new的结果,在这种情况下你可以只使用不同的变量名)
  • @RichardCritten 这不是问题,因为在每个显式析构函数调用之后立即构造一个新实例。
猜你喜欢
  • 2019-06-04
  • 1970-01-01
  • 2020-04-21
  • 1970-01-01
  • 1970-01-01
  • 2019-12-07
  • 1970-01-01
  • 2023-03-07
  • 2018-04-22
相关资源
最近更新 更多