C++:重新绑定泛型类型的变量

C++ : rebind variable of generic type

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

我想做的事情:

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 值是反弹的,但是这仍然有一个关联的类型约束。

使用新位置重新绑定

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

表达式T t = r-value;不调用构造函数,同理表达式new (&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

如果您愿意为堆栈上的额外布尔值付费,像 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 函数。