I have a unusual situation,
Say I have a class like the following,
template <typename T>
class C
{
public :
C (int size) : value_(size), some_other_member_(size) {}
T &value () {return value_;}
const T &value() const {return value_;}
private :
T value_;
SomeOtherType some_other_member_;
};
The class designed such that the client can have full access to the member value_, just like std::vector's operator[] will return a reference, this class also have to give client the full access through returning a reference. A setter/getter pair will not do.
However, unlike std::vector, I don't want to allow the client to be able to replace the member entirely. That is, the client shall be able to call const or non-const members of value_, but the following shall not be allowed,
C<SomeType> c(10);
SomeType another_value(5);
c.value() = another_value; // This shall not be allowed
Is there any possible way that I can give client the full access to value_. In some sense, the class C shall be like a container, but once the thing it contained is initialized (through the constructor, there are requirements to T, but that is not relevant here), client can only modify value_ through T's member functions, not replace value_ by assignment.
However, requires T be un-copyable is not an option to me. Because the class C can be copied. At the core of the problem, is that as seen in the class C, C has a few members, they all have a size property, when constructed, they are all constructed with the same size, if value_ is allowed to be replaced through assignment, then it allows the data structure being corrupt in the sense that members may no longer have the same size property.
Requiring T to only allow copy or assignment when the size is the same is also not an option. Because, when copy a C object, the size can be different between the source and the destination. For example,
C c1(10);
C c2(20);
c1 = c2;
is perfectly reasonable. The size of c1 is changed, but all of its members are also changed to the same new size, so it is OK.
I hope I have stated the problem clear. I summary, I want C does not pose much restriction on T, T can be basically any type with a required constructor. T can be copied and assigned. The only thin I don't want client to do is assignment to value_ through C::value().
If you want the user to be able to call non-const member functions on the object and you want to return a reference to the actualy object, you can't completely prohibit assignment, since the assignment operator is basically just that (you can rewrite a = b as a.operator=(b)). Therefore you either need to return only a const reference to your object, make the contained object non_copyable or live with the fact, that it can be assigned to.
Personally I would suggesting rethinking the design. Even if you could disallow assignment, there are really no guarantees that the object doesn't have a member function, which does basically the same think (.swap(...) is a typical candidate), so you haven't really won anything as long as you allow calling non const memberfunctions.
However if you are only concerned with disallowing accidential assignments, you can make it harder to make such an assignment. If your T isn't a builtin, you could create a derived class, which doesn't expose a public assignment operator and return a reference to that:
template <typename T>
class C{
public :
class Derived: public T {
private:
Derived(int size):T(size) {}
Derived& operator=(const Derived&) = default; //used C++11 syntax for brevity, for C++03 code it has to be implemented here
Derived(const Derived&) = default; //don't want this class to be copyied outside of C
~Derived() = default;
friend class C;
};
C (int size) : value_(size), some_other_member_(size) {}
Derived& value () {return value_;}
const Derived& value() const {return value_;}
private :
Derived value_;
SomeOtherType some_other_member_;
};
This will give access to all public members by inheritence, but hide assignment operator (and constructors). Of course if you use c++11, this code could be enhanced by using/defining move constructors/assignments and using perfect forwarding, to allow different constructors. Note that the T-part of Derived can still be assigned to using static_cast<T&>(C.value()) = foo;
To support types you can't derive from (builtins...), you'd need to create a proxy, which exposes all functionality except assignments.
As to your getter/setter problem, I would write
const T& value() const; // as getter
void value(const T&); // as setter
Returning const T& (const-reference) is exactly against situations like c.value() = 10 (see eg. Effective C++ by Scott Meyers, item 23).
I think this also solves the copy problem: your class remains copyable.
Related
I mostly work on system-level C++ projects that don't allow exceptions to be thrown, but RAII is (rightfully) strongly encouraged. Right now, we handle the lack of failing constructors using infamous tricks many C++ programmers are familiar with, like:
Trivial constructor followed by a call to bool init(Args...) to do the hard stuff
Real constructor followed by checking bool is_valid() const
Heap-allocating with static unique_ptr<MyType> create(Args...)
Of course, these all have drawbacks (heap allocation, invalid and "moved" states, etc).
My company is finally updating compilers and will allow glorious C++17 to be used. Since C++17 features std::optional<T> and, most importantly, mandatory copy elision, I was hoping I could greatly simplify all our classes into something that would look like this:
class MyType {
public:
static std::optional<MyType> create() {
// If any of the hard stuff fails, return std::nullopt
return std::optional<MyType>(std::in_place, 5, 'c');
}
~MyType() {
// Cleanup mArg0 and mArg1, which are always valid if the object exists
}
// ... class functionality ...
// Disable default constructor, move, and copy.
// None of these are needed because mandatory copy elision
// allows the static function above to return rvalue without
// copy or move operations
MyType() = delete;
MyType(const MyType&) = delete;
MyType(MyType&&) = delete;
MyType& operator=(const MyType&) = delete;
MyType& operator=(MyType&&) = delete;
private:
MyType(ArgT0 arg0, ArgT1 arg1) : mArg0(arg0), mArg1(arg1) {}
ArgT0 mArg0;
ArgT1 mArg1;
};
Notice how nice this is: Static function ensures all the hard stuff is done before the object is ever created, lack of default ctor/move means object never exists in an invalid or moved state, private constructor ensures user can't accidentally skip the named ctor.
Unfortunately, because the ctor is private, the std::is_constructable_t<MyType> check fails and therefore the in_place constructor of optional is SFINAE'd out.
This code works if I do one of 2 things, neither of which I want to:
Make the ctor public (But now users of the class can accidentally circumvent the named ctor)
Allow the move operations (But now I have to deal with invalidated objects)
I have also tried this, but it doesn't work because std::optional required a move operator for this to work:
static std::optional<MyType> create() {
// If any of the hard stuff fails, return std::nullopt
return std::optional<MyType>(MyType(5, 'c'));
}
Is there some trick or incantation I may be missing to get this to work, or have I hit the limits of what C++17 will allow?
Thanks!
If you want to make any indirect object construction work (emplace in its various forms, in_place constructors of optional, make_shared, etc) , the constructor in question must be public. You can make a constructor public without allowing all public use by using something called a private key.
Basically, you create a type (call it Key) whose default constructor is private. The class has no members, nor does it do anything. It declares that MyType is a friend of Key; this means that only members of MyType can construct one.
Now, make all of MyType's constructors public, but they all take a Key const& as the first parameter. This means that in theory anyone could call them, but in practice only someone who has a Key instance can actually call them. Members of MyType can create such an instance, and they can pass those instances to optional's in_place constructor or any other indirect mechanism. This effectively gives the indirect construction mechanism private access to the constructor.
This is a standard idiom for dealing with forwarding of private access to a type. Indeed, one could hypothetically write a generic key<T> type like this:
template<typename T>
class key
{
private:
key() = default;
key(int) {} //Not an aggregate
friend T;
};
One small note. Because of an annoyance of C++11 pre-C++20, any type with no members and no constructors other than defaulted/deleted copy/move/default constructors is considered an aggregate. This is true even if you explicitly = default its default constructor. As such, that type can undergo aggregate initialization, which has no public/private distinction. That is, anybody could call your private-key constructors by doing this: MyType({}, <params>);.
To avoid this, you will need to give Key an additional (private) constructor or otherwise prevent it from being an aggregate.
std::unique_ptr has a deleted copy constructor, which means that if you have a unique_ptr in your class Foo as a data member then you must write your own copy constructor for Foo and manually deep-copy that member (even if the compiler-generated copy constructor would be fine for all other members).
In order to be able to copy in a polymorphic way, the clone() method pattern can be used. Let's assume our objects have a clone method like this:
class Base {
virtual std::unique_ptr<Base> clone() = 0;
};
Foo looks like this now:
class Foo {
public:
...
Foo(Foo const& other)
: b(other.b->clone())
, // init 10 more members that could otherwise be auto-copied just fine
// with the automatically generated copy constructor
{}
...
private:
std::unique_ptr<Base> b;
//10 more data members
};
Now, I found a way to auto-clone Foo::b, by writing a wrapper over unique_ptr that defines the copy constructor and assignment by calling clone.
template <typename T>
class auto_cloned_unique_ptr
{
private:
std::unique_ptr<T> up;
public:
// copy constructor
auto_cloned_unique_ptr(auto_cloned_unique_ptr<T> const& other)
: up(other.up->clone()) {}
// copy assignment
auto_cloned_unique_ptr<T>& operator =(auto_cloned_unique_ptr<T> const& other)
{
this->up = other.up->clone();
return *this;
}
auto_cloned_unique_ptr(std::unique_ptr<T> _up)
: up(std::move(_up)) {}
// Delegate everything else to unique_ptr
auto_cloned_unique_ptr(auto_cloned_unique_ptr<T>&& other)
: up(std::move(other.up)) {}
auto_cloned_unique_ptr<T>& operator =(auto_cloned_unique_ptr<T>&& other)
{
this->up = std::move(other.up);
return *this;
}
auto operator *() const {return *up;}
auto operator->() const {return up.operator->();}
auto get() -> const {return up.get();}
};
Now if we use this we don't need to define our own copy constructor:
class Foo2 {
public:
...
private:
auto_cloned_unique_ptr<Base> b;
//10 more data members
};
Is such an approach very much frowned upon (for using a non-standard wrapper over unique_ptr)?
Let me first paraphrase what you want to do: You want that each instance of Foo has its own instance of Base in b; in particular, if you copy a Foo, the copy will have its own new Base, initially with the same "value". In other words, Base should behave like a value.
At the same time, you can't store Base directly in Foo because it is an abstract class. In other words, you want b to be polymorphic.
There you have it: you want a polymorphic value. Other people have recognized this need and proposed for C++20 as polymorphic_value<Base>. From the documentation:
The class template, polymorphic_value, confers value-like semantics on
a free-store allocated object. A polymorphic_value may hold an
object of a class publicly derived from T, and copying the
polymorphic_value will copy the object of the derived type.
It has a reference implementation that you can use as of now. Very simply put, it is an wrapper around std::unique_ptr similar to what you propose.
The problem with your approach is that it is changing the meaning of a unique_ptr. The key thing about a unique_ptr is that it tells who is the owner of an object. If you add a copy constructor for unique_ptr, what does that mean? Are you copying the ownership? A and B both uniquely own the thing? That does not make sense. If they share ownership, then you should be using the shared_ptr to indicate the shared ownership. If you want to have a unique owner of a copy of the object, you would naturally indicate that by make_unique(*pFoo). With base and derived objects, having the base object have a
virtual unique_ptr<Foo> Clone() const=0;
is a perfectly normal construct. That is, the derived classes know how to copy themselves so they don't produce a sliced copy, but they return a unique_ptr to the base class to indicate that you will own the copy they have produced. Within these clone operations, yes, you will need to explicitly handle non-copyable members of the derived classes, so you won't be able to just use a default or generated copy constructor. You need to answer "what does it mean to copy something that contains this thing that can't be copied?"
As a concrete example, what would it mean to copy a derived class that had a mutex? What if it were locked and another thread were waiting on it? See why it's hard to give a general answer?
This approach is fine, but you should be very carefull not to clone your objects when you did not intentd to.
Also inherriting from unique_ptr might improve performance
I often find myself writing tedious move constructors for classes with many member variables. They look something like the following:
A(A && rhs) :
a(std::move(rhs.a)),
b(std::move(rhs.b)),
c(std::move(rhs.c)),
d(std::move(rhs.d)) {
some_extra_work();
}
That is, they perform all of the actions associated with the default move constructor, then peform some mundane extra task. Ideally I would delegate to the default move constructor then perform the extra work, however the act of defining my own move constructor prevents the default implementation from being defined, meaning there's nothing to delegate to.
Is there a nice way to get around this anti-pattern?
Update: ignore the first part of this answer and skip to the end which has a better solution.
Wrap the extra work in a new type and inherit from it:
class A;
struct EW
{
EW(EW&&);
};
class A : private EW
{
friend class EW;
public:
A(A&&) = default;
};
EW::EW(EW&&) { A* self = static_cast<A*>(this); self->some_extra_work(); }
You could also do it with a data member instead of a base class, but you'd need some hackery using offsetof (which is undefined for non-standard-layout types) or a hand-rolled equivalent using sneaky pointer arithmetic. Using inheritance allows you to use static_cast for the conversion.
This won't work if some_extra_work() has to be done after the members are initialized because base classes are initialized first.
Alternatively if the extra work is actually operating on the rvalue object that you're moving from, then you should wrap the members in types that do that work automatically when moved from, e.g. my tidy_ptr type, which I use to implement the Rule of Zero
class A
{
tidy_ptr<D> d;
public:
A() = default;
A(const A&) = default;
A(A&& r) = default; // Postcondition: r.d == nullptr
};
I have a class with a member which is not changed by the methods of the class, so I marked it as const. My problem is that I was using the default assignment operator just like a copy constructor in order to avoid multiple declarations. But in this case the assignment operator is not automatically generated, so I get some compiler errors:
'operator =' function is unavailable. This seems like that there is no real life scenario where const class members can be actually used (e.g. have you seen any const member in the STL code?).
Is there any way to fix this, beside removing the const?
EDIT: some code
class A
{
public :
const int size;
A(const char* str) : size(strlen(str)) {}
A() : size(0) {}
};
A create(const char* param)
{
return A(param);
}
void myMethod()
{
A a;
a = create("abcdef");
// do something
a = create("xyz");
// do something
}
Here's your misconception which is causing this issue:
[..] which is not changed by the methods of the class
The member variable is changed by a method of your class, the assignment operator. Including the one synthesized by the compiler. If you mark a member variable as const, this expresses that this variable will (should not!) change its value during the lifetime of the object. So clearly, assigning a new value to the object violates this statement. So if you indeed don't want the member to change, just don't make it const.
const members are ideal in many, many cases. of course, there is the obvious case where a value should not or must not change, but it's also an important restriction for optimization and concurrency -- not every type needs or should have an assignment operator.
if the member needs the behavior of assignment, then the variable must not be const.
when the value/member must not mutate or be mutated by this, it's clearer to provide a separate interface for the variable members (or even a subtype->composition in more complex cases):
class t_text {
public:
// ...
public:
void setString(const std::string& p);
private:
const t_text_attributes d_attributes;
std::string d_string;
};
therefore, my suggestion is to hide the assignment operator, and to make the 'mutable chunk' or member set-able for clarity:
text.setString("TEXT"); // << Good: What you read is what happens.
text = otherText; // << Bad: Surprise - attributes don't actually change!
You cannot have a const member and support assignment, at least not
assignment with the expected semantics. const is, logically, a
promiss that the member will never change, and assignment is
(implicitly, in the minds of most people) a promiss that all data
members will take the values of the members of the right hand side
(which normally means changing). There's a very definite conflict
between these two promisses.
Of course, a lot of types shouldn't support assignment to begin with;
for types that don't support assignment, there's no problem declaring a
data member const. On the whole, however, I've found const a lot
less useful here; const is part of a contract, and data members are
not usually part of the external contract of the class. (But a lot
depends—if the data member is public or protected, then the fact
that it is immutable could be part of the external contract. And of
course, there's nothing wrong with expressing internal class invariants
in language constructs, either.)
Yes, you can override the assignment operator.
Because you're using the default one, the compiler will try to copy the const members also. Which is illegal, since it's const.
class A
{
private:
const int a;
public :
A() : a(0) {}
A& operator = (const A& other) {return *this;}
};
int main()
{
A a;
A b;
a = b; //this is legal if operator = is declared
}
While learning the concept of "copying members", the book gives the following statement.
In addition, a default assignment cannot be generated if a nonstatic member is a reference, a const,or a user-defined type without a copy assignment.
I do not quite understand what does this statement really want to deliver? Or which kind of scenario does this statement refer to? Thanks.
This statement has to do with the compiler automatically generating the default assignment operator function for a class you write (i.e. user-defined type). The default assignment works by copying all the members over to a new instance. This statement covers three cases where a default assignment would not be able to be generated:
1) When a member is a reference (i.e. refers to an instance of a variable, like a pointer)
class Foop {
int& reference;
};
2) When a member variable is constant
class Foople {
const int someConst;
};
3) When some other class does not have a copy-constructor and you have a member variable of that type, obviously it cannot be copied using the default method (which uses copy-constructors)
class Uncopyable {
private:
Uncopyable(Uncopyable const& other);
};
class Fleep {
Uncopyable uncopyable;
};
In these cases, you would need to write your own assignment operator (or possibly do without).
If you have a member in your class which is not static (shared between all instances of class), and is either
a reference (high level pointer)
a constant
a user-defined type with dynamic data (the same as the class we're talking about)
The default = operator and copy constructor is no longer valid and you should write manual versions of those.
class ClassA
{
int& _myReferenceMember;
const int _myConstant;
ClassB _objWhereClassBHasNoCopyConstructor;
}
Above are examples of the three cases you described. And as you quoted, you must write a custom copy constructor (if you want a copy constructor at all) in such a case, or change your member variables.
It refers to the distinction between:
class A { int a; };
and
class B { int& a; };
For class A, the compiler will generate an implicit assignment operator (=), but in the case of B, it cannot. This is because references in C++ don't have pointer semantics. i.e. you cannot change what a reference point to after it is constructed, hence, the implicit copy constructor would not be able to copy that member. The same thing goes for const members (which are explicitly marked as being immutable) and members which don't have a assignment operators (implicit or explicit).
The default assignment operator for A would essentially do this:
class A
{
A& operator=(A const& a_) { a = a_.a; }
int a;
};