I have a RAII class which manages a resource. The problem is, copying the resource requires an additional parameter which is in no way related to the resource, rather is an argument to the resource copy operation.
Thus I have a class copy constructor which requires an additional argument. This is allowed provided the additional argument has a default value. I want to write copy assignment and move assignment operators (preferably using the copy-swap idiom), but those do not allow an additional argument. I could make that troublesome argument a class member, but then users are likely to use it incorrectly, as it would need to be set before every copy or move assignment call.
Any ideas how to approach this?
struct Foo{
void* ptr;
Foo(size_t){/*allocate*/}
~Foo(){/*deallocate*/}
Foo(const Foo& that, int x = 0 /*required additional parameter for copying*/){CopyMethod(*this,that,x);}
Foo& operator=(Foo);// cannot implement without int x parameter
};
CodePudding user response:
There is no absolute requirement in C that a copy or a move must be done by operator=
or by a constructor.
All that does is allow a copy or a move to result from a natural use of the =
operator, or natural object construction.
But there is no universal rule in C as to what copy or move must do, or what it means. It means, in practical terms, whatever the class want it to mean. Unsurprisingly, a copy or a move can also be implemented by some random class method, perhaps named copy_from
or move_from
.
I could make that troublesome argument a class member, but then users are likely to use it incorrectly,
Delete the copy/move constructor/operator.
Implement class methods that effect a copy or a move from another instance of this object. Those methods can have hundreds of parameters, if needed. That would be quite cumbersome, of course. In your case just one parameter won't be much of a bother.
So, in the end, a copy or a move gets effected by cheaply default-constructing a new object, and then invoking the appropriate class method, forcing your users to spell out what all the required parameters are, and logically eliminating the possibility of misusing the class's copy/move semantics.
It's also possible not to delete the default constructors or operators, but perhaps implement them in some meaningful way, to effect a copy or a move with the default parameter values, and allowing usage of =
in most common use cases, but requiring a named class method when things must happen out of the ordinary.
When it comes to C , the only thing that's etched in stone is its grammar. There is no way to stick some additional values in the vicinity of any natural =
operator, and have it sucked into a copy or a move constructor. Hence it's not possible to make the default copy/move assignment work this way. But nothing prevents a copy or a move to be implemented by something other than an old-fashioned=
. Sure, more typing is required to explicitly invoke a named class method, but it is what it is, C 's grammar is immutable.
Also, keep in mind that the C library containers have absolutely no knowledge, whatsoever, about any extra parameters your object requires to be copied or moved. They don't care. Shoving this object in a vector requires, at least, default copy semantics. There is no way to use your object with std::vector
if it requires some explicit value, of some kind, to be copied or moved. It's a default copy constructor/assignment, or no dice.
CodePudding user response:
As a suggestion, it may be useful to store "context" such as the active CUDA stream in a thread-local variable in order to avoid giving it explicitly to each function. Something like this:
class WithStream
{
// cudaStream_t is just an opaque pointer so this is safe
static thread_local cudaStream_t current;
cudaStream_t prev;
public:
constexpr WithStream() noexcept
: prev()
{}
explicit WithStream(cudaStream_t activate) noexcept
: prev(current)
{ current = activate; }
~WithStream()
{
if(prev)
current = prev;
}
static cudaStream_t active() noexcept
{ return current; }
};
This allows a usage like this:
class Allocation
{
void* mem;
public:
Allocation(const Allocation& o)
: mem(...)
{
cudaMemcpyAsync(mem, o.mem, ..., WithStream::active());
}
};
std::vector<Allocation> copy(const std::vector<Allocation>& o, cudaStream_t stream)
{
WithStream activate(stream);
return std::vector<Allocation>(o);
}
One may argue that this introduces global state which is a code smell. That is justifiable. However, CUDA already contains this kind of global state: The active device is handled in such a manner. Therefore we don't make things much worse.