Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3-5. vectorへのemplace_back/push_back説明への違和感 #72

Closed
yohhoy opened this issue Nov 27, 2020 · 9 comments · Fixed by #85
Closed

3-5. vectorへのemplace_back/push_back説明への違和感 #72

yohhoy opened this issue Nov 27, 2020 · 9 comments · Fixed by #85

Comments

@yohhoy
Copy link

yohhoy commented Nov 27, 2020

#56 で追加された下記ノート(question)は、強すぎる主張により読者の誤解を招く表現になっていると感じます。

C++11より前は push_back という関数のみが末尾への要素追加を担っていました。 C++11で追加された emplace_back は要素型のコンストラクタに直接引数を渡すことができるので push_back と同じかそれ以上のパフォーマンスを得られます。 多くの処理系では push_back を実装するのに emplace_back を呼び出していますから 現代において push_back を使うべき理由は存在しません。

emplace_backが導入されたC++11以降でも、既存変数からのコピー/ムーブを行うといったpush_backのユースケースは依然として残っています。emplace_backが有効のなのは、一部のコンテナにおいて巨大な要素オブジェクト型を格納するさいにメモリ確保を効率化できる可能性があるためです(導入当時のMotivation)。その意味では、vector<int>例示における実質的なemplace_back/push_back差異はなく、やはり少々強すぎる主張と思います。
訂正:既存変数からのコピー/ムーブはemplace_backでも実現可能でした。この部分は取り下げます。

struct S { std::string a, b; };
std::vector<S> v;

v.push_back({"abc", "xyz"});
// ↓の方が好ましい(ことが多い)
v.emplace_back("abc", "xyz");
@yohhoy
Copy link
Author

yohhoy commented Nov 27, 2020

本件に関して、StackOverflow上の議論 Why would I ever use push_back instead of emplace_back? がありました。

暗黙(implicit)/明示(explicit)コンストラクタの違いにフォーカスした例示があげられています。

std::vector<std::unique_ptr<T>> v;
T a;
v.emplace_back(std::addressof(a)); // compiles
v.push_back(std::addressof(a)); // fails to compile

かなりマニアックなケースであることは否めませんが、「すべてのpush_backemplace_backで代替すべき」の反例として挙げておきます。

@wx257osn2
Copy link

wx257osn2 commented Nov 27, 2020

struct S { std::string a, b; };
std::vector<S> v;

v.push_back({"abc", "xyz"});
// ↓の方が好ましい(ことが多い)
v.emplace_back("abc", "xyz");

『ゼロから学ぶC++』はC++11を対象としているので, emplace_back だと集成体初期化は行えない(上記コードの v.emplace_back("abc", "xyz") はコンパイルエラーになる )ため, push_back の方が簡潔に記述できる,ということはあると思います.

struct S { std::string a, b; };
std::vector<S> v;

v.push_back({"abc", "xyz"});  // compiles
v.emplace_back("abc", "xyz"); // fails to compile
v.emplace_back({"abc", "xyz"}); // fails to compile
v.emplace_back(S{"abc", "xyz"}); // compiles

@yumetodo
Copy link
Contributor

まあ全部のケースでpush_backを置き換えられるわけではないというのはたしかにそうですね。どう修正しましょうか・・・。


ところでClangで下のコード通らないのなんででしょう・・・

#include <string>
#include <vector>

int main()
{
    struct S { std::string a, b; };
    std::vector<S> v;

    v.push_back({"abc", "xyz"});  // compiles
    v.emplace_back("abc", "xyz"); // fails to compile
    //v.emplace_back({"abc", "xyz"}); // fails to compile
    v.emplace_back(S{"abc", "xyz"}); // compiles
}

https://wandbox.org/permlink/hmwRtwbKIC8NPC9b

In file included from prog.cc:1:
In file included from /opt/wandbox/clang-head/include/c++/v1/string:506:
In file included from /opt/wandbox/clang-head/include/c++/v1/string_view:175:
In file included from /opt/wandbox/clang-head/include/c++/v1/__string:57:
In file included from /opt/wandbox/clang-head/include/c++/v1/algorithm:643:
/opt/wandbox/clang-head/include/c++/v1/memory:1600:17: error: no matching function for call to 'construct_at'
                _VSTD::construct_at(__p, _VSTD::forward<_Args>(__args)...);
                ^~~~~~~~~~~~~~~~~~~
/opt/wandbox/clang-head/include/c++/v1/__config:828:15: note: expanded from macro '_VSTD'
#define _VSTD std::_LIBCPP_ABI_NAMESPACE
              ^
/opt/wandbox/clang-head/include/c++/v1/memory:1442:14: note: in instantiation of function template specialization 'std::__1::allocator_traits<std::__1::allocator<S>>::__construct<S, char const (&)[4], char const (&)[4]>' requested here
            {__construct(__has_construct<allocator_type, _Tp*, _Args...>(),
             ^
/opt/wandbox/clang-head/include/c++/v1/vector:926:21: note: in instantiation of function template specialization 'std::__1::allocator_traits<std::__1::allocator<S>>::construct<S, char const (&)[4], char const (&)[4]>' requested here
    __alloc_traits::construct(this->__alloc(), _VSTD::__to_address(__tx.__pos_),
                    ^
/opt/wandbox/clang-head/include/c++/v1/vector:1687:9: note: in instantiation of function template specialization 'std::__1::vector<S, std::__1::allocator<S>>::__construct_one_at_end<char const (&)[4], char const (&)[4]>' requested here
        __construct_one_at_end(_VSTD::forward<_Args>(__args)...);
        ^
prog.cc:10:7: note: in instantiation of function template specialization 'std::__1::vector<S, std::__1::allocator<S>>::emplace_back<char const (&)[4], char const (&)[4]>' requested here
    v.emplace_back("abc", "xyz"); // fails to compile
      ^
/opt/wandbox/clang-head/include/c++/v1/memory:903:16: note: candidate template ignored: substitution failure [with _Tp = S, _Args = <char const (&)[4], char const (&)[4]>]: no matching constructor for initialization of 'S'
constexpr _Tp* construct_at(_Tp* __location, _Args&& ...__args) {
               ^
1 error generated.

@yohhoy
Copy link
Author

yohhoy commented Nov 27, 2020

ところでClangで下のコード通らないのなんででしょう・・・

StackOverflow回答 Does clang++ 12 support C++20 std::construct_at? によれば、単に未実装のようです。

@wx257osn2
Copy link

Clang,ちょいちょいC++20サポートが貧弱なので…

@yohhoy
Copy link
Author

yohhoy commented Nov 27, 2020

まあ全部のケースでpush_backを置き換えられるわけではないというのはたしかにそうですね。
どう修正しましょうか・・・。

例えば 書籍 Effective Modern C++ では「項目42:要素の挿入の代わりに直接配置を検討する」と紹介されていますね。
push_backemplace_backの最適な使い分けまで考えるとサイト趣旨を超えてしまうため、高度なトピックとして紹介するにとどめる程度が現実解かなと思います。


素案:

C++11より前は push_back という関数のみが末尾への要素追加を担っていました。 C++11で追加された emplace_back は要素型のコンストラクタに直接引数を渡すことができるので push_back と同じかそれ以上のパフォーマンスを得られるケースがあります。両者の最適な使い分けは高度なトピックとなるため、詳細は 書籍 Effective Modern C++ で紹介される「項目42:要素の挿入の代わりに直接配置を検討する」を参照ください。

@yumetodo
Copy link
Contributor

うーん、自分はまずemplace_back を使ってみてエラーになったらpush_backという使い方をしてきたんですが、それじゃだめですかね

@yohhoy
Copy link
Author

yohhoy commented Nov 27, 2020

emplace_back を使ってみてエラーになったらpush_backという使い方

改めて書籍 Effective Modern C++(日本語版) の記載を確認してみましたが、yumetodo さん方針では セキュアコーディング の観点から不安が残ります。

前掲のStackOverflow回答にも言及ありますが、push_backにくらべてemplace_back強力な機能 です。

セマンティクス上はコンパイルエラーとなるべき(例えばpush_backであればコンパイル時に検出できる問題)ケースでもemplace_backではコンストラクトできてしまい、実行時に発生する未定義動作により困難なデバッグ作業を強いられる可能性を否めません。以下、書籍よりサンプルを引用します。

#include <vector>
#include <regex>

int main()
{
  std::vector<std::regex> rv;

  rv.emplace_back(nullptr);  // 実行時エラー
  // rv.push_back(nullptr);  // コンパイルエラー
}

@yumetodo
Copy link
Contributor

セキュアコーディング の観点

なるほど、そこには頭が回っていなかったですね・・・。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants