Skip to content

Commit aa2e381

Browse files
committed
Make Node move constructor and assignment operator noexcept (#809)
Move constructor: * m_isValid (bool) exchange(rhs.m_isValid, true) * m_invalidKey (std::string) std::move() * m_pMemory (shared_memory_holder) std::move() * m_pNode (node*) exchange(rhs.m_pNode, nullptr) This leaves the moved-from Node as if it was just default constructed. Move assignment: A sanity test is performed to check if it's a valid move, and if not: *this is returned (with an added assert() for debugging). A temporary Node is move constructed (using the move constructor), leaving the moved-from Node as if it was just default constructed. If this->m_pNode == nullptr, the same operation as AssignNode would do is done and *this is returned. if temporary.m_pNode == nullptr: m_pNode->set_null() swap(*this, temporary) return *this; Otherwise the merge step that AssignNode would do if both m_pNodes are not nullptr is done, using a new member function, AssignNodeDetail(). Signed-off-by: Ted Lyngmo <ted@lyncon.se>
1 parent a564e18 commit aa2e381

File tree

3 files changed

+91
-2
lines changed

3 files changed

+91
-2
lines changed

include/yaml-cpp/node/impl.h

+69-1
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,29 @@
1212
#include "yaml-cpp/node/detail/node.h"
1313
#include "yaml-cpp/node/iterator.h"
1414
#include "yaml-cpp/node/node.h"
15+
#include "yaml-cpp/noexcept.h"
16+
#include <cassert>
1517
#include <sstream>
1618
#include <string>
19+
#include <utility>
1720

1821
namespace YAML {
19-
inline Node::Node()
22+
namespace detail {
23+
#if __cplusplus >= 201402L
24+
using ::std::exchange;
25+
#else
26+
template<class T, class U = T>
27+
T exchange(T& obj, U&& new_value) {
28+
T old_value = std::move(obj);
29+
obj = std::forward<U>(new_value);
30+
return old_value;
31+
}
32+
#endif
33+
} // namespace detail
34+
} // namespace YAML
35+
36+
namespace YAML {
37+
inline Node::Node() YAML_CPP_NOEXCEPT
2038
: m_isValid(true), m_invalidKey{}, m_pMemory(nullptr), m_pNode(nullptr) {}
2139

2240
inline Node::Node(NodeType::value type)
@@ -44,6 +62,13 @@ inline Node::Node(const detail::iterator_value& rhs)
4462

4563
inline Node::Node(const Node& rhs) = default;
4664

65+
inline Node::Node(Node&& rhs) YAML_CPP_NOEXCEPT
66+
: m_isValid(detail::exchange(rhs.m_isValid, true)),
67+
m_invalidKey(std::move(rhs.m_invalidKey)),
68+
m_pMemory(std::move(rhs.m_pMemory)),
69+
m_pNode(detail::exchange(rhs.m_pNode, nullptr)) {
70+
}
71+
4772
inline Node::Node(Zombie)
4873
: m_isValid(false), m_invalidKey{}, m_pMemory{}, m_pNode(nullptr) {}
4974

@@ -188,6 +213,13 @@ inline void Node::SetStyle(EmitterStyle::value style) {
188213
}
189214

190215
// assignment
216+
inline bool Node::CheckValidMove(const Node& rhs) const YAML_CPP_NOEXCEPT {
217+
// Note: Both m_pNode's can be nullptr.
218+
return
219+
m_isValid && rhs.m_isValid &&
220+
(!m_pNode || !rhs.m_pNode || !m_pNode->is(*rhs.m_pNode));
221+
}
222+
191223
inline bool Node::is(const Node& rhs) const {
192224
if (!m_isValid || !rhs.m_isValid)
193225
throw InvalidNode(m_invalidKey);
@@ -209,6 +241,38 @@ inline Node& Node::operator=(const Node& rhs) {
209241
return *this;
210242
}
211243

244+
inline Node& Node::operator=(Node&& rhs) YAML_CPP_NOEXCEPT {
245+
if (!CheckValidMove(rhs)) {
246+
assert(false);
247+
return *this;
248+
}
249+
250+
Node tmp(std::move(rhs));
251+
252+
if (!m_pNode) {
253+
// we don't have a node so do what AssignNode does in this case
254+
m_pNode = tmp.m_pNode;
255+
m_pMemory = tmp.m_pMemory;
256+
return *this;
257+
}
258+
259+
if (!tmp.m_pNode) {
260+
// We have an m_pNode but tmp doesn't. Instead of calling tmp.EnsureNodeExists(),
261+
// which can potentially throw, and then AssignNodeDetail, we do set_null() on it
262+
// and swap with the empty, but valid, tmp. While this does not ensure m_pNode
263+
// to exist afterwards as before (quite the opposite), it leaves us in a valid
264+
// state.
265+
m_pNode->set_null();
266+
std::swap(*this, tmp);
267+
return *this;
268+
}
269+
270+
// Both m_pNode's are present and the merge should (hopefully) be noexcept.
271+
AssignNodeDetail(tmp);
272+
273+
return *this;
274+
}
275+
212276
inline void Node::reset(const YAML::Node& rhs) {
213277
if (!m_isValid || !rhs.m_isValid)
214278
throw InvalidNode(m_invalidKey);
@@ -258,6 +322,10 @@ inline void Node::AssignNode(const Node& rhs) {
258322
return;
259323
}
260324

325+
AssignNodeDetail(rhs);
326+
}
327+
328+
inline void Node::AssignNodeDetail(const Node& rhs) YAML_CPP_NOEXCEPT {
261329
m_pNode->set_ref(*rhs.m_pNode);
262330
m_pMemory->merge(*rhs.m_pMemory);
263331
m_pNode = rhs.m_pNode;

include/yaml-cpp/node/node.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "yaml-cpp/node/detail/iterator_fwd.h"
1717
#include "yaml-cpp/node/ptr.h"
1818
#include "yaml-cpp/node/type.h"
19+
#include "yaml-cpp/noexcept.h"
1920

2021
namespace YAML {
2122
namespace detail {
@@ -41,12 +42,13 @@ class YAML_CPP_API Node {
4142
using iterator = YAML::iterator;
4243
using const_iterator = YAML::const_iterator;
4344

44-
Node();
45+
Node() YAML_CPP_NOEXCEPT;
4546
explicit Node(NodeType::value type);
4647
template <typename T>
4748
explicit Node(const T& rhs);
4849
explicit Node(const detail::iterator_value& rhs);
4950
Node(const Node& rhs);
51+
Node(Node&& rhs) YAML_CPP_NOEXCEPT;
5052
~Node();
5153

5254
YAML::Mark Mark() const;
@@ -77,10 +79,12 @@ class YAML_CPP_API Node {
7779
void SetStyle(EmitterStyle::value style);
7880

7981
// assignment
82+
bool CheckValidMove(const Node& rhs) const YAML_CPP_NOEXCEPT;
8083
bool is(const Node& rhs) const;
8184
template <typename T>
8285
Node& operator=(const T& rhs);
8386
Node& operator=(const Node& rhs);
87+
Node& operator=(Node&& rhs) YAML_CPP_NOEXCEPT;
8488
void reset(const Node& rhs = Node());
8589

8690
// size/iterator
@@ -128,6 +132,7 @@ class YAML_CPP_API Node {
128132

129133
void AssignData(const Node& rhs);
130134
void AssignNode(const Node& rhs);
135+
void AssignNodeDetail(const Node& rhs) YAML_CPP_NOEXCEPT;
131136

132137
private:
133138
bool m_isValid;

test/node/node_test.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,22 @@ TEST(NodeTest, EqualRepresentationAfterMoveAssignment) {
112112
EXPECT_EQ(ss1.str(), ss2.str());
113113
}
114114

115+
TEST(NodeTest, NodeIsNullWhenMovedFromByCtor) {
116+
Node node1;
117+
node1[1] = 1;
118+
Node node2 = std::move(node1);
119+
EXPECT_TRUE(node1.IsNull());
120+
}
121+
122+
TEST(NodeTest, NodeIsNullWhenMovedFromByAssignment) {
123+
Node node1;
124+
Node node2;
125+
node1[1] = 1;
126+
node2[2] = 2;
127+
node2 = std::move(node1);
128+
EXPECT_TRUE(node1.IsNull());
129+
}
130+
115131
TEST(NodeTest, MapElementRemoval) {
116132
Node node;
117133
node["foo"] = "bar";

0 commit comments

Comments
 (0)