0

ここ最近考えていた問題です。インターフェイスが、コピーにコストがかかり、移動にコストがかかるオブジェクトを返すメンバー関数であるとしましょう (std::string、std::vector など)。結果を計算して一時オブジェクトを返す実装もあれば、単にメンバー オブジェクトを返す実装もあります。

説明するサンプル コード:

// assume the interface is: Vec foo() const
// Vec is cheap to move but expensive to copy

struct RetMember {
    Vec foo() const { return m_data; }
    Vec m_data;
    // some other code
}

struct RetLocal {
    Vec foo() const {
        Vec local = /*some computation*/;
        return local;
    }
};

さまざまな「クライアント」もあります。データを読み取るだけのものもあれば、所有権が必要なものもあります。

void only_reads(const Vec&) { /* some code */ }
void requires_ownership(Vec) { /* some code */ }

上記のコードは適切に構成されていますが、可能な限り効率的ではありません。すべての組み合わせは次のとおりです。

RetMember retmem;
RetLocal retloc;

only_reads(retmem.foo()); // unnecessary copy, bad
only_reads(retloc.foo()); // no copy, good

requires_ownership(retmem.foo()); // copy, good
requires_ownership(retloc.foo()); // no copy, good

この状況を修正する良い方法は何ですか?

私は2つの方法を思いつきましたが、もっと良い解決策があると確信しています。

私の最初の試みでは、T の値または const T へのポインターのいずれかを保持する DelayedCopy ラッパーを作成しました。これは非常に見苦しく、余分な労力が必要であり、余分な移動が発生し、コピーの省略が妨げられ、おそらく他にも多くの問題があります。

2 番目に考えたのは、継続渡しスタイルです。これは非常にうまく機能しますが、メンバー関数をメンバー関数テンプレートに変換します。std::function があることは知っていますが、オーバーヘッドがあるため、パフォーマンスに関しては受け入れられない場合があります。

サンプルコード:

#include <boost/variant/variant.hpp>
#include <cstdio>
#include <iostream>
#include <type_traits>

struct Noisy {

  Noisy() = default;
  Noisy(const Noisy &) { std::puts("Noisy: copy ctor"); }
  Noisy(Noisy &&) { std::puts("Noisy: move ctor"); }

  Noisy &operator=(const Noisy &) {
    std::puts("Noisy: copy assign");
    return *this;
  }
  Noisy &operator=(Noisy &&) {
    std::puts("Noisy: move assign");
    return *this;
  }
};

template <typename T> struct Borrowed {
  explicit Borrowed(const T *ptr) : data_(ptr) {}
  const T *get() const { return data_; }

private:
  const T *data_;
};

template <typename T> struct DelayedCopy {
private:
  using Ptr = Borrowed<T>;
  boost::variant<Ptr, T> data_;

  static_assert(std::is_move_constructible<T>::value, "");
  static_assert(std::is_copy_constructible<T>::value, "");

public:
  DelayedCopy() = delete;

  DelayedCopy(const DelayedCopy &) = delete;
  DelayedCopy &operator=(const DelayedCopy &) = delete;

  DelayedCopy(DelayedCopy &&) = default;
  DelayedCopy &operator=(DelayedCopy &&) = default;

  DelayedCopy(T &&value) : data_(std::move(value)) {}
  DelayedCopy(const T &cref) : data_(Borrowed<T>(&cref)) {}

  const T &ref() const { return boost::apply_visitor(RefVisitor(), data_); }

  friend T take_ownership(DelayedCopy &&cow) {
    return boost::apply_visitor(TakeOwnershipVisitor(), cow.data_);
  }

private:
  struct RefVisitor : public boost::static_visitor<const T &> {
    const T &operator()(Borrowed<T> ptr) const { return *ptr.get(); }
    const T &operator()(const T &ref) const { return ref; }
  };

  struct TakeOwnershipVisitor : public boost::static_visitor<T> {
    T operator()(Borrowed<T> ptr) const { return T(*ptr.get()); }
    T operator()(T &ref) const { return T(std::move(ref)); }
  };
};

struct Bar {
  Noisy data_;

  auto fl() -> DelayedCopy<Noisy> { return Noisy(); }
  auto fm() -> DelayedCopy<Noisy> { return data_; }

  template <typename Fn> void cpsl(Fn fn) { fn(Noisy()); }
  template <typename Fn> void cpsm(Fn fn) { fn(data_); }
};

static void client_observes(const Noisy &) { std::puts(__func__); }
static void client_requires_ownership(Noisy) { std::puts(__func__); }

int main() {
  Bar a;

  std::puts("DelayedCopy:");
  auto afl = a.fl();
  auto afm = a.fm();

  client_observes(afl.ref());
  client_observes(afm.ref());

  client_requires_ownership(take_ownership(a.fl()));
  client_requires_ownership(take_ownership(a.fm()));

  std::puts("\nCPS:");

  a.cpsl(client_observes);
  a.cpsm(client_observes);

  a.cpsl(client_requires_ownership);
  a.cpsm(client_requires_ownership);
}

出力:

DelayedCopy:
Noisy: move ctor
client_observes
client_observes
Noisy: move ctor
Noisy: move ctor
client_requires_ownership
Noisy: copy ctor
client_requires_ownership

CPS:
client_observes
client_observes
client_requires_ownership
Noisy: copy ctor
client_requires_ownership

余分なコピーを回避する値を渡すためのより良い手法はありますか?

余談ですが、コードは C++11 の g++ 5.2 および clang 3.7 でコンパイルされています。C++14 および C++1z では、DelayedCopy はコンパイルされず、それが私のせいかどうかわかりません。

4

1 に答える 1

1

おそらく何千もの「正しい」方法があります。私は次のいずれかを好むでしょう:

  1. 参照または移動されたオブジェクトを提供する方法は明示的に述べられているため、誰も疑うことはありません。
  2. 維持するコードをできるだけ少なくします。
  3. すべてのコードの組み合わせがコンパイルされ、賢明なことを行います。

この(不自然な)例のようなもの:

#include <iostream>
#include <string>
#include <boost/optional.hpp>

// an object that produces (for example) strings
struct universal_producer
{
    void produce(std::string s)
    {
        _current = std::move(s);
        // perhaps signal clients that there is something to take here?
    }

    // allows a consumer to see the string but does not relinquish ownership
    const std::string& peek() const {
        // will throw an exception if there is nothing to take
        return _current.value();
    }

    // removes the string from the producer and hands it to the consumer
    std::string take() // not const
    {
        std::string result = std::move(_current.value());
        _current = boost::none;
        return result;
    }

    boost::optional<std::string> _current;

};

using namespace std;

// prints a string by reference
void say_reference(const std::string& s)
{
    cout << s << endl;
}

// prints a string after taking ownership or a copy depending on the call context
void say_copy(std::string s)
{
    cout << s << endl;
}

auto main() -> int
{
    universal_producer producer;
    producer.produce("Hello, World!");

    // print by reference
    say_reference(producer.peek());

    // print a copy but don't take ownership
    say_copy(producer.peek());

    // take ownership and print
    say_copy(producer.take());
    // producer now has no string. next peek or take will cause an exception
    try {
        say_reference(producer.peek());
    }
    catch(const std::exception& e)
    {
        cout << "exception: " << e.what() << endl;
    }
    return 0;
}

期待される出力:

Hello, World!
Hello, World!
Hello, World!
exception: Attempted to access the value of an uninitialized optional object.
于 2015-10-03T11:48:57.533 に答える