在没有移动函数的情况下返回不可复制对象的解决方法

Workaround for returning uncopyable object without a move ctor

本文关键字:可复制 对象 方法 解决 返回 情况下 移动 函数      更新时间:2023-10-16

在我的API中,我有一个返回std::istringstream的函数。
std::istringstream类是不可复制的,但支持在符合标准的编译器上移动,因此返回本地std::istringstream没有问题。

但是,在gcc 4.9中,不支持移动std::istringstream。是否有一些变通办法,我可以使用std::istringstream,而不改变API从用户的角度?

此处建议使用unique_ptr<std::istringstream>的解决方案将改变API的语义。

如果你不能移动std::istringstream,那就没有什么办法了。

如果一个对象是不可复制和不可移动的,你不能按值返回它。如果你想要支持新特性,你最好为这些特性买一个新的编译器。

在此期间,您可以返回unique_ptr。如果您真的希望按值返回,您可以返回一个包含std::unique_ptr<std::istringstream>的可移动包装器,并提供与istringstream相同的接口。但是,这也会影响返回类型。

通过右值引用返回可能很诱人。你可以这样做:

struct MyApiClass {
    std::istringstream&& get_stream() {
        return std::move(*_stream);
    }
private:
    std::unique_ptr<std::istringstream> _stream;
};

然后,在旧的编译器中,你可以这样使用它:

std::istringstream&& stream = myApiClass.get_stream();
// use stream as long as myApiClass exists

使用新编译器的人可以这样使用它:

std::istringstream stream = myApiClass.get_stream();
// use stream normally

这是api受影响较小的方式。除此之外,我不知道有什么办法。

不使用move/copy构造函数返回class的方法是使用带大括号init-list的返回语句:

class C {
    C() = default;
    C(const C&) = delete;
    C(C&&) = delete;
};
C make_C() { return {}; }
int main() {
    C&& c = make_C();
}

演示

不幸的是,这个初始化只考虑非显式构造函数,而std::istringstream有显式构造函数。

一种解决方法是创建一个带有非显式构造函数的子类:
struct myIStringStream : std::istringstream
{
    myIStringStream () = default;
};
myIStringStream make_istringstream()
{
    return {};
}
int main()
{
    std::istringstream&& iss = make_istringstream();
}
演示

回答我自己的问题,以供完整和将来参考。

我们的目标是为gcc (<5) std::istringstream不提供移动函数的bug,在我想返回不可复制和(bug -)不可移动流的情况下,该函数将起作用。
正如在评论中提到的,我实际上可以改变我的函数签名(至少在gcc <5)返回一个代理对象,允许在不改变API的情况下复制或移动新编译器/其他编译器上使用的代码。

这个想法,由一个同事建议和实现,是在std::istringstream周围创建一个代理对象,它提供了类似的API,但也提供了一个复制-ctor,它从复制-from流手动创建和初始化一个新的内部std::istringstream。这个代理只在有问题的编译器上使用。

其自然栖息地的代码在这里。
以下是相关部分:

#if !defined(__GNUC__) || (__GNUC__ >= 5)
   using string_stream = std::istringstream;
#else
    // Until GCC 5, istringstream did not have a move constructor.
    // stringstream_proxy is used instead, as a workaround.
   class stringstream_proxy
   {
   public:
      stringstream_proxy() = default;
      // Construct with a value.
      stringstream_proxy(std::string const& value) :
         stream_(value)
      {}
      // Copy constructor.
      stringstream_proxy(const stringstream_proxy& other) :
         stream_(other.stream_.str())
      {
         stream_.setstate(other.stream_.rdstate());
      }
      void setstate(std::ios_base::iostate state) { stream_.setstate(state); }
      // Stream out the value of the parameter.
      // If the conversion was not possible, the stream will enter the fail state,
      // and operator bool will return false.
      template<typename T>
      stringstream_proxy& operator >> (T& thing)
      {
         stream_ >> thing;
         return *this;
      }

      // Get the string value.
      std::string str() const { return stream_.str(); }
      std::stringbuf* rdbuf() const { return stream_.rdbuf(); }
      // Check the state of the stream. 
      // False when the most recent stream operation failed
      operator bool() const { return !!stream_; }
      ~stringstream_proxy() = default;
   private:
      std::istringstream stream_;
   };
   using string_stream = stringstream_proxy;
#endif