std::tuple with non moveable/copyable type
up vote
2
down vote
favorite
Assume I have a struct MyStruct
which contains a std::mutex
and only reference or pod members. I want to create a std::tuple
containing this type. But as MyStruct
is not copy- or moveable it doen't work. It is working if I add a move constructor in the following way:
#include <tuple>
#include <mutex>
#include <vector>
struct MyStruct {
std::vector<double>& vec_;
int number_;
std::mutex mutex_;
MyStruct(std::vector<double>& vec, const int number)
: vec_(vec), number_(number) {}
MyStruct(MyStruct&& o) : vec_(o.vec_), number_(o.number_)
{
std::lock_guard guard(o.mutex_);
}
};
int main()
{
using T = std::tuple<MyStruct,int>;
std::vector<double> v{1., 2., 3.};
T t{MyStruct{v, 1}, 2};
}
Is this safe? In the end I only want to construct it inside the std::tuple
before any multi-threading is going on, but how can I ensure that the elements of the std::tuple
are not moved once the std::tuple
is constructed? Or is there a way to construct a std::tuple
in place?
c++
|
show 1 more comment
up vote
2
down vote
favorite
Assume I have a struct MyStruct
which contains a std::mutex
and only reference or pod members. I want to create a std::tuple
containing this type. But as MyStruct
is not copy- or moveable it doen't work. It is working if I add a move constructor in the following way:
#include <tuple>
#include <mutex>
#include <vector>
struct MyStruct {
std::vector<double>& vec_;
int number_;
std::mutex mutex_;
MyStruct(std::vector<double>& vec, const int number)
: vec_(vec), number_(number) {}
MyStruct(MyStruct&& o) : vec_(o.vec_), number_(o.number_)
{
std::lock_guard guard(o.mutex_);
}
};
int main()
{
using T = std::tuple<MyStruct,int>;
std::vector<double> v{1., 2., 3.};
T t{MyStruct{v, 1}, 2};
}
Is this safe? In the end I only want to construct it inside the std::tuple
before any multi-threading is going on, but how can I ensure that the elements of the std::tuple
are not moved once the std::tuple
is constructed? Or is there a way to construct a std::tuple
in place?
c++
afaik, you can construct a pair in-place, not a tuple
– Piotr Skotnicki
Nov 19 at 12:54
Won'tauto tuple = std::make_tuple<MyStruct, int>({v, 1}, 2);
work?
– Ted Lyngmo
Nov 19 at 12:59
Not with gcc 8.2.0.
– Max
Nov 19 at 13:01
g++ 8.2.1 accepts it - but I'm not sure it's really constructed in-place. Btw, do you really mean to have a reference to the vector of doubles?
– Ted Lyngmo
Nov 19 at 13:04
Also, moving a mutex is ok, so you could probably justdelete
the copy ctor and copy assignment operator and make the move ctor and move assignmentdefault
.
– Ted Lyngmo
Nov 19 at 13:11
|
show 1 more comment
up vote
2
down vote
favorite
up vote
2
down vote
favorite
Assume I have a struct MyStruct
which contains a std::mutex
and only reference or pod members. I want to create a std::tuple
containing this type. But as MyStruct
is not copy- or moveable it doen't work. It is working if I add a move constructor in the following way:
#include <tuple>
#include <mutex>
#include <vector>
struct MyStruct {
std::vector<double>& vec_;
int number_;
std::mutex mutex_;
MyStruct(std::vector<double>& vec, const int number)
: vec_(vec), number_(number) {}
MyStruct(MyStruct&& o) : vec_(o.vec_), number_(o.number_)
{
std::lock_guard guard(o.mutex_);
}
};
int main()
{
using T = std::tuple<MyStruct,int>;
std::vector<double> v{1., 2., 3.};
T t{MyStruct{v, 1}, 2};
}
Is this safe? In the end I only want to construct it inside the std::tuple
before any multi-threading is going on, but how can I ensure that the elements of the std::tuple
are not moved once the std::tuple
is constructed? Or is there a way to construct a std::tuple
in place?
c++
Assume I have a struct MyStruct
which contains a std::mutex
and only reference or pod members. I want to create a std::tuple
containing this type. But as MyStruct
is not copy- or moveable it doen't work. It is working if I add a move constructor in the following way:
#include <tuple>
#include <mutex>
#include <vector>
struct MyStruct {
std::vector<double>& vec_;
int number_;
std::mutex mutex_;
MyStruct(std::vector<double>& vec, const int number)
: vec_(vec), number_(number) {}
MyStruct(MyStruct&& o) : vec_(o.vec_), number_(o.number_)
{
std::lock_guard guard(o.mutex_);
}
};
int main()
{
using T = std::tuple<MyStruct,int>;
std::vector<double> v{1., 2., 3.};
T t{MyStruct{v, 1}, 2};
}
Is this safe? In the end I only want to construct it inside the std::tuple
before any multi-threading is going on, but how can I ensure that the elements of the std::tuple
are not moved once the std::tuple
is constructed? Or is there a way to construct a std::tuple
in place?
c++
c++
asked Nov 19 at 12:42
Max
242112
242112
afaik, you can construct a pair in-place, not a tuple
– Piotr Skotnicki
Nov 19 at 12:54
Won'tauto tuple = std::make_tuple<MyStruct, int>({v, 1}, 2);
work?
– Ted Lyngmo
Nov 19 at 12:59
Not with gcc 8.2.0.
– Max
Nov 19 at 13:01
g++ 8.2.1 accepts it - but I'm not sure it's really constructed in-place. Btw, do you really mean to have a reference to the vector of doubles?
– Ted Lyngmo
Nov 19 at 13:04
Also, moving a mutex is ok, so you could probably justdelete
the copy ctor and copy assignment operator and make the move ctor and move assignmentdefault
.
– Ted Lyngmo
Nov 19 at 13:11
|
show 1 more comment
afaik, you can construct a pair in-place, not a tuple
– Piotr Skotnicki
Nov 19 at 12:54
Won'tauto tuple = std::make_tuple<MyStruct, int>({v, 1}, 2);
work?
– Ted Lyngmo
Nov 19 at 12:59
Not with gcc 8.2.0.
– Max
Nov 19 at 13:01
g++ 8.2.1 accepts it - but I'm not sure it's really constructed in-place. Btw, do you really mean to have a reference to the vector of doubles?
– Ted Lyngmo
Nov 19 at 13:04
Also, moving a mutex is ok, so you could probably justdelete
the copy ctor and copy assignment operator and make the move ctor and move assignmentdefault
.
– Ted Lyngmo
Nov 19 at 13:11
afaik, you can construct a pair in-place, not a tuple
– Piotr Skotnicki
Nov 19 at 12:54
afaik, you can construct a pair in-place, not a tuple
– Piotr Skotnicki
Nov 19 at 12:54
Won't
auto tuple = std::make_tuple<MyStruct, int>({v, 1}, 2);
work?– Ted Lyngmo
Nov 19 at 12:59
Won't
auto tuple = std::make_tuple<MyStruct, int>({v, 1}, 2);
work?– Ted Lyngmo
Nov 19 at 12:59
Not with gcc 8.2.0.
– Max
Nov 19 at 13:01
Not with gcc 8.2.0.
– Max
Nov 19 at 13:01
g++ 8.2.1 accepts it - but I'm not sure it's really constructed in-place. Btw, do you really mean to have a reference to the vector of doubles?
– Ted Lyngmo
Nov 19 at 13:04
g++ 8.2.1 accepts it - but I'm not sure it's really constructed in-place. Btw, do you really mean to have a reference to the vector of doubles?
– Ted Lyngmo
Nov 19 at 13:04
Also, moving a mutex is ok, so you could probably just
delete
the copy ctor and copy assignment operator and make the move ctor and move assignment default
.– Ted Lyngmo
Nov 19 at 13:11
Also, moving a mutex is ok, so you could probably just
delete
the copy ctor and copy assignment operator and make the move ctor and move assignment default
.– Ted Lyngmo
Nov 19 at 13:11
|
show 1 more comment
1 Answer
1
active
oldest
votes
up vote
1
down vote
accepted
Your code may be safe in particular circumstances, but if you want it to be safe in general, then you need to fix up your move constructor. The move constructor must properly synchronize access to the moved-from object's state, and it must prevent that object from modifying the state once the new object has stolen it:
class MyStruct {
std::vector<double>* vec_; // use a pointer here, not a reference
int number_;
std::mutex mutex_;
public:
MyStruct(std::vector<double>& vec, const int number)
: vec_(&vec), number_(number) {}
MyStruct(MyStruct&& o)
{
std::lock_guard guard(o.mutex_);
vec_ = std::exchange(o.vec_, nullptr);
number_ = std::exchange(o.number_, 0); // doesn't necessarily have to be 0 here
}
};
You will also have to change any other methods to account for the moved-from state. Also note that I've changed this to a class and made the state private. Having a public mutex and public data members invites trouble. For safety, you must ensure that all access to the mutable state is properly synchronized.
Without the Move Constructor
If you want to avoid the move constructor, then the most obvious solution is to avoid using a tuple
. tuple
is intended for use with generic code in libraries. Valid uses in other contexts exist but are rare. Consider using a struct to store the values instead:
struct Fixture
{
MyStruct a;
int b;
};
This has the advantage of naming your members (which is more readable) and allowing custom constructors.
Using a std::tuple
If you absolutely must use a tuple, then the problem is still solvable. You just need a way to construct your MyStruct
object after the tuple is constructed. There a few ways you might do that. Here are two:
Use std::optional
:
int main()
{
using T = std::tuple<std::optional<MyStruct>,int>;
std::vector<double> v{1., 2., 3.};
T t{std::nullopt, 2};
std::get<0>(t).emplace(v, 1);
}
Define a default constructor and use two-phase initialization:
// Add a constructor like the following, once again using a pointer instead of reference
MyStruct() : vec_(nullptr), number_(0) {}
// Add an init method
void init(std::vector<double>& vec, int number)
{
vec_ = &vec;
number_ = number;
}
// Later
int main()
{
using T = std::tuple<MyStruct,int>;
std::vector<double> v{1., 2., 3.};
T t{MyStruct{}, 2};
std::get<0>(t).init(v, 1);
}
add a comment |
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
accepted
Your code may be safe in particular circumstances, but if you want it to be safe in general, then you need to fix up your move constructor. The move constructor must properly synchronize access to the moved-from object's state, and it must prevent that object from modifying the state once the new object has stolen it:
class MyStruct {
std::vector<double>* vec_; // use a pointer here, not a reference
int number_;
std::mutex mutex_;
public:
MyStruct(std::vector<double>& vec, const int number)
: vec_(&vec), number_(number) {}
MyStruct(MyStruct&& o)
{
std::lock_guard guard(o.mutex_);
vec_ = std::exchange(o.vec_, nullptr);
number_ = std::exchange(o.number_, 0); // doesn't necessarily have to be 0 here
}
};
You will also have to change any other methods to account for the moved-from state. Also note that I've changed this to a class and made the state private. Having a public mutex and public data members invites trouble. For safety, you must ensure that all access to the mutable state is properly synchronized.
Without the Move Constructor
If you want to avoid the move constructor, then the most obvious solution is to avoid using a tuple
. tuple
is intended for use with generic code in libraries. Valid uses in other contexts exist but are rare. Consider using a struct to store the values instead:
struct Fixture
{
MyStruct a;
int b;
};
This has the advantage of naming your members (which is more readable) and allowing custom constructors.
Using a std::tuple
If you absolutely must use a tuple, then the problem is still solvable. You just need a way to construct your MyStruct
object after the tuple is constructed. There a few ways you might do that. Here are two:
Use std::optional
:
int main()
{
using T = std::tuple<std::optional<MyStruct>,int>;
std::vector<double> v{1., 2., 3.};
T t{std::nullopt, 2};
std::get<0>(t).emplace(v, 1);
}
Define a default constructor and use two-phase initialization:
// Add a constructor like the following, once again using a pointer instead of reference
MyStruct() : vec_(nullptr), number_(0) {}
// Add an init method
void init(std::vector<double>& vec, int number)
{
vec_ = &vec;
number_ = number;
}
// Later
int main()
{
using T = std::tuple<MyStruct,int>;
std::vector<double> v{1., 2., 3.};
T t{MyStruct{}, 2};
std::get<0>(t).init(v, 1);
}
add a comment |
up vote
1
down vote
accepted
Your code may be safe in particular circumstances, but if you want it to be safe in general, then you need to fix up your move constructor. The move constructor must properly synchronize access to the moved-from object's state, and it must prevent that object from modifying the state once the new object has stolen it:
class MyStruct {
std::vector<double>* vec_; // use a pointer here, not a reference
int number_;
std::mutex mutex_;
public:
MyStruct(std::vector<double>& vec, const int number)
: vec_(&vec), number_(number) {}
MyStruct(MyStruct&& o)
{
std::lock_guard guard(o.mutex_);
vec_ = std::exchange(o.vec_, nullptr);
number_ = std::exchange(o.number_, 0); // doesn't necessarily have to be 0 here
}
};
You will also have to change any other methods to account for the moved-from state. Also note that I've changed this to a class and made the state private. Having a public mutex and public data members invites trouble. For safety, you must ensure that all access to the mutable state is properly synchronized.
Without the Move Constructor
If you want to avoid the move constructor, then the most obvious solution is to avoid using a tuple
. tuple
is intended for use with generic code in libraries. Valid uses in other contexts exist but are rare. Consider using a struct to store the values instead:
struct Fixture
{
MyStruct a;
int b;
};
This has the advantage of naming your members (which is more readable) and allowing custom constructors.
Using a std::tuple
If you absolutely must use a tuple, then the problem is still solvable. You just need a way to construct your MyStruct
object after the tuple is constructed. There a few ways you might do that. Here are two:
Use std::optional
:
int main()
{
using T = std::tuple<std::optional<MyStruct>,int>;
std::vector<double> v{1., 2., 3.};
T t{std::nullopt, 2};
std::get<0>(t).emplace(v, 1);
}
Define a default constructor and use two-phase initialization:
// Add a constructor like the following, once again using a pointer instead of reference
MyStruct() : vec_(nullptr), number_(0) {}
// Add an init method
void init(std::vector<double>& vec, int number)
{
vec_ = &vec;
number_ = number;
}
// Later
int main()
{
using T = std::tuple<MyStruct,int>;
std::vector<double> v{1., 2., 3.};
T t{MyStruct{}, 2};
std::get<0>(t).init(v, 1);
}
add a comment |
up vote
1
down vote
accepted
up vote
1
down vote
accepted
Your code may be safe in particular circumstances, but if you want it to be safe in general, then you need to fix up your move constructor. The move constructor must properly synchronize access to the moved-from object's state, and it must prevent that object from modifying the state once the new object has stolen it:
class MyStruct {
std::vector<double>* vec_; // use a pointer here, not a reference
int number_;
std::mutex mutex_;
public:
MyStruct(std::vector<double>& vec, const int number)
: vec_(&vec), number_(number) {}
MyStruct(MyStruct&& o)
{
std::lock_guard guard(o.mutex_);
vec_ = std::exchange(o.vec_, nullptr);
number_ = std::exchange(o.number_, 0); // doesn't necessarily have to be 0 here
}
};
You will also have to change any other methods to account for the moved-from state. Also note that I've changed this to a class and made the state private. Having a public mutex and public data members invites trouble. For safety, you must ensure that all access to the mutable state is properly synchronized.
Without the Move Constructor
If you want to avoid the move constructor, then the most obvious solution is to avoid using a tuple
. tuple
is intended for use with generic code in libraries. Valid uses in other contexts exist but are rare. Consider using a struct to store the values instead:
struct Fixture
{
MyStruct a;
int b;
};
This has the advantage of naming your members (which is more readable) and allowing custom constructors.
Using a std::tuple
If you absolutely must use a tuple, then the problem is still solvable. You just need a way to construct your MyStruct
object after the tuple is constructed. There a few ways you might do that. Here are two:
Use std::optional
:
int main()
{
using T = std::tuple<std::optional<MyStruct>,int>;
std::vector<double> v{1., 2., 3.};
T t{std::nullopt, 2};
std::get<0>(t).emplace(v, 1);
}
Define a default constructor and use two-phase initialization:
// Add a constructor like the following, once again using a pointer instead of reference
MyStruct() : vec_(nullptr), number_(0) {}
// Add an init method
void init(std::vector<double>& vec, int number)
{
vec_ = &vec;
number_ = number;
}
// Later
int main()
{
using T = std::tuple<MyStruct,int>;
std::vector<double> v{1., 2., 3.};
T t{MyStruct{}, 2};
std::get<0>(t).init(v, 1);
}
Your code may be safe in particular circumstances, but if you want it to be safe in general, then you need to fix up your move constructor. The move constructor must properly synchronize access to the moved-from object's state, and it must prevent that object from modifying the state once the new object has stolen it:
class MyStruct {
std::vector<double>* vec_; // use a pointer here, not a reference
int number_;
std::mutex mutex_;
public:
MyStruct(std::vector<double>& vec, const int number)
: vec_(&vec), number_(number) {}
MyStruct(MyStruct&& o)
{
std::lock_guard guard(o.mutex_);
vec_ = std::exchange(o.vec_, nullptr);
number_ = std::exchange(o.number_, 0); // doesn't necessarily have to be 0 here
}
};
You will also have to change any other methods to account for the moved-from state. Also note that I've changed this to a class and made the state private. Having a public mutex and public data members invites trouble. For safety, you must ensure that all access to the mutable state is properly synchronized.
Without the Move Constructor
If you want to avoid the move constructor, then the most obvious solution is to avoid using a tuple
. tuple
is intended for use with generic code in libraries. Valid uses in other contexts exist but are rare. Consider using a struct to store the values instead:
struct Fixture
{
MyStruct a;
int b;
};
This has the advantage of naming your members (which is more readable) and allowing custom constructors.
Using a std::tuple
If you absolutely must use a tuple, then the problem is still solvable. You just need a way to construct your MyStruct
object after the tuple is constructed. There a few ways you might do that. Here are two:
Use std::optional
:
int main()
{
using T = std::tuple<std::optional<MyStruct>,int>;
std::vector<double> v{1., 2., 3.};
T t{std::nullopt, 2};
std::get<0>(t).emplace(v, 1);
}
Define a default constructor and use two-phase initialization:
// Add a constructor like the following, once again using a pointer instead of reference
MyStruct() : vec_(nullptr), number_(0) {}
// Add an init method
void init(std::vector<double>& vec, int number)
{
vec_ = &vec;
number_ = number;
}
// Later
int main()
{
using T = std::tuple<MyStruct,int>;
std::vector<double> v{1., 2., 3.};
T t{MyStruct{}, 2};
std::get<0>(t).init(v, 1);
}
edited Nov 19 at 15:45
answered Nov 19 at 15:34
Peter Ruderman
10.1k2352
10.1k2352
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53374906%2fstdtuple-with-non-moveable-copyable-type%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
afaik, you can construct a pair in-place, not a tuple
– Piotr Skotnicki
Nov 19 at 12:54
Won't
auto tuple = std::make_tuple<MyStruct, int>({v, 1}, 2);
work?– Ted Lyngmo
Nov 19 at 12:59
Not with gcc 8.2.0.
– Max
Nov 19 at 13:01
g++ 8.2.1 accepts it - but I'm not sure it's really constructed in-place. Btw, do you really mean to have a reference to the vector of doubles?
– Ted Lyngmo
Nov 19 at 13:04
Also, moving a mutex is ok, so you could probably just
delete
the copy ctor and copy assignment operator and make the move ctor and move assignmentdefault
.– Ted Lyngmo
Nov 19 at 13:11