Is const reference to a global constant good practice? - c++

Trying to figure out how to efficiently and safely reference global custom type constants that are initialized at compile time. I want to avoid copying of Foo as I will be creating a lot of Bar instances and Foo is a bit more complicated than shown here. Keeping a const reference from Bar to Foo appears to be safe (or is it?) but at the same time I don't know how to prevent the "badBar" usage if possible. Any suggestions?
class Foo
{
int a_, b_;
public:
constexpr Foo(int a, int b) : a_(a), b_(b) {}
};
inline constexpr Foo kFoo{1, 2};
class Bar
{
const Foo& foo_;
public:
Bar(const Foo& foo) : foo_(foo) {}
const Foo& foo() { return foo_; }
};
int main() {
Bar goodBar(kFoo);
Bar badBar(Foo(3, 4)); // ouch
return 0;
}

Yes, keeping a reference to globally defined constant variable is safe as it will always be valid.
You can just initialize the member directly:
class Foo
{
int a_, b_;
public:
constexpr Foo(int a, int b) : a_(a), b_(b) {}
};
inline constexpr Foo kFoo{1, 2};
class Bar
{
const Foo& foo_=kFoo;
};
The rest of the world does not have to know it exists and makes the ctor of Bar simpler.
Although it feels slightly redundant, anywhere you can write foo_, you can also write kFoo. Maybe I am misunderstanding your question?

You can add an rvalue constructor to discard rvalue arguments.
class Bar
{
const Foo& foo_;
public:
Bar(const Foo& foo) : foo_(foo) {}
Bar(Foo &&) = delete; // <-- rvalue constructor
const Foo& foo() { return foo_; }
};

Related

C++ re-assignable const member variable

In C++, suppose I have a member variable of type Bar, and I want to make sure only const methods are called on it. So then AFAIK I can declare it as a const Bar member variable like below:
class Foo
{
public:
Foo(const Bar& b) : bar(b) {}
void setBar(Bar& b) {
bar = b; // compiler complains here
}
private:
const Bar bar;
}
But I also want to be able to re-assign bar altogether. Is this possible to do without the compiler complaining?
The "trick" is to have your member data be a std::shared_ptr<const Bar> instead of const Bar; you can then re-assign the shared_ptr at will, but can't change Bar since the pointer is const.
#include <memory>
struct Bar final {};
struct Foo final
{
//Foo(const Bar& b) : bar(b) {}
Foo(const Bar& b) : pBar(std::make_shared<const Bar>(b)) {}
void setBar(const Bar& b) {
//bar = b; // compiler complains here
pBar = std::make_shared<const Bar>(b);
}
private:
//const Bar bar;
std::shared_ptr<const Bar> pBar;
};
Note that std::unique_ptr might be better than std::shared_ptr, but then you've got to add copy/assignment, assuming you want those. However, since it's a shared_ptr to constBar, it's not all that bad.
const members create more problems than they solve. If you want to make sure to only call const methods on bar but you want it not to be const (because you want to reassign it) then you can add another layer of encapsulation. For example:
struct Bar {
void doSomething() const {}
};
struct BarHolder {
BarHolder(const Bar& b) : bar(b) {}
const Bar& get() {return bar;}
void set(const Bar& b) { bar = b;}
private:
Bar bar;
};
class Foo {
public:
Foo(const Bar& b) : bar(b) {}
void doSomething() {
bar.get().doSomething(); // <- can only call const methods
}
void setBar(Bar& b) {
bar.set(b);
}
private:
BarHolder bar;
};
It didn't occur to me at first, but as mentioned in a comment, you similarly get "another layer of encapsulation" by using a std::unique_ptr<const Bar>, though then the object will be actually const.
If you want the Bar member variable to be re-assignable, then you don't want it to be const. This doesn't mean you can't call the const member functions on it; it just means that if a competing non-const overload exists, it will call the non-const one -- for example:
struct Bar {
// (1)
auto get_baz() -> Baz&;
// (2)
auto get_baz() const -> const Baz&;
};
Bar bar;
const Bar cbar;
auto& b1 = bar.get_baz(); // calls (1) because bar is not const
auto& b2 = cbar.get_baz(); // calls (2) because cbar is const
This issue does not happen if there are no non-const competing overloads:
struct Bar {
auto get_buzz() const -> const Buzz&;
};
Bar bar;
const Bar cbar;
// These both call the one get_buzz
auto& b1 = bar.get_buzz();
auto& b2 = cbar.get_buzz();
If you want to explicitly force the call to the const overload, you just need to make sure you call it from a const-qualified object or reference instead. This can be done easily with c++17's std::as_const
const Baz& b = std::as_const(bar).get_baz(); // calls (2)
If you don't have access to C++17, then you could also just form a const reference to Bar either by using a static_cast, or by using a temporary const reference, such as:
const Baz& b = static_cast<const Bar&>(bar).get_baz();
// or
const auto& const_bar = bar;
const Baz& b = const_bar.get_baz();
The first thing I'd point out is that your understanding of "const" methods is a bit off: a const method does not mean that it can modify const members, but that it can be called on a const instance of that class.
Eg if you have
class Foo {
public:
void method1() const { ... };
void method2() { ... };
}
and then a const instance of Foo as
const Foo foo1;
then you could call
foo1.method1()
becuase method1 is const, as is foo1; but you could not call method2, because foo1 is const, and method2 is not.
That said, what you are looking for is probably the "mutable" keyword on your member: usually a const method will not allow changing any members of that class (well, because the instance of that calss is const!); however, you can tag individual members of the class to be "mutable", which will allow const methods to change that member.
Eg, if you have
class Foo { public:
int a;
mutable int b;
void method() const { ... }
}
Then Foo::method() would be allowed to change 'a', but not 'b'.
Once you declare a member of a class as const, then only the constructor can set this member.

Idiomatic way to prevent slicing?

Sometimes it can be an annoyance that c++ defaults to allow slicing. For example
struct foo { int a; };
struct bar : foo { int b; };
int main() {
bar x{1,2};
foo y = x; // <- I dont want this to compile!
}
This compiles and runs as expected! Though, what if I dont want to enable slicing?
What is the idiomatic way to write foo such that one cannot slice instances of any derived class?
I'm not sure if there is a named idiom for it but you can add a deleted function to the overload set that is a better match then the base classes slicing operations. If you change foo to
struct foo
{
int a;
foo() = default; // you have to add this because of the template constructor
template<typename T>
foo(const T&) = delete; // error trying to copy anything but a foo
template<typename T>
foo& operator=(const T&) = delete; // error assigning anything else but a foo
};
then you can only ever copy construct or copy assign a foo to foo. Any other type will pick the function template and you'll get an error about using a deleted function. This does mean that your class, and the classes that use it can no longer be an aggregate though. Since the members that are added are templates, they are not considered copy constructors or copy assignment operators so you'll get the default copy and move constructors and assignment operators.
Since 2011, the idiomatic way has been to use auto:
#include <iostream>
struct foo { int a; };
struct bar : foo { int b; };
int main() {
bar x{1,2};
auto y = x; // <- y is a bar
}
If you wish to actively prevent slicing, there are a number of ways:
Usually the most preferable way, unless you specifically need inheritance (you often don't) is to use encapsulation:
#include <iostream>
struct foo { int a; };
struct bar
{
bar(int a, int b)
: foo_(a)
, b(b)
{}
int b;
int get_a() const { return foo_.a; }
private:
foo foo_;
};
int main() {
bar x{1,2};
// foo y = x; // <- does not compile
}
Another more specialised way might be to alter the permissions around copy operators:
#include <iostream>
struct foo {
int a;
protected:
foo(foo const&) = default;
foo(foo&&) = default;
foo& operator=(foo const&) = default;
foo& operator=(foo&&) = default;
};
struct bar : foo
{
bar(int a, int b)
: foo{a}, b{b}
{}
int b;
};
int main() {
auto x = bar (1,2);
// foo y = x; // <- does not compile
}
You can prevent the base from being copied outside of member functions of derived classes and the base itself by declaring the copy constructor protected:
struct foo {
// ...
protected:
foo(foo&) = default;
};

How to assign the end of a reference chain?

For example I have a class:
class Foo {
public:
Foo(const Foo& foo) : father(foo) {}
private:
const Foo& father;
};
How to assign the father field if the object is the top?
I tried Foo foo(foo);, but the compiler warn me the foo is uninitialized, I guess the compiler only assign memory to the foo object after all initialization are done, so if I do this way, the father will reference to some wild memory address.
So, In this case, how to assign the father right if the object is the top?
Use a special constructor (and use a tag to distinguish your constructor from the copy constructor):
struct father_tag {};
class Foo {
public:
Foo(const Foo& foo, father_tag) : father(foo) {}
Foo() : father(*this) {}
private:
const Foo& father;
};
// usage:
Foo father;
Foo next(father, father_tag{});
or you can use a pointer instead of a reference, leaving it to nullptr at the end of the chain. Then you can check with if (father) if you are at the end or not:
class Foo {
public:
Foo(Foo const* pfather) : m_pfather(pfather) {}
Foo() : m_pfather(nullptr) {}
private:
Foo const* m_pfather;
};

Call to implicitly deleted copy constructor

I have the following setup:
class MyClass {
public:
static MyClass Clone(const MyClass& other) {
return MyClass(other, 10);
}
static MyClass CreateNormal(int x, int y) {
return MyClass(int x, int y);
}
// There are a few more factory functions.
private:
// Constructor 1
MyClass(int x, int y) : b_(x, y) {}
// Constructor 2
MyClass(const MyClass& other, int c) : b_(other.b_, c) {}
// And a lot more constructors.
// NotMyClass has its copy constructor deleted.
NotMyClass b_;
}
int main() {
MyClass A(1,2);
MyClass B = MyClass::Clone(A); // ERROR
}
Is there a way to get around this without modifying NotMyClass?
The error happens because the copy constructor of MyClass is implicitly deleted. Is there a way I can use std::Move() to solve this?
I cannot remove the factory functions as the actual class has many constructors and the factory functions are needed for clarity and understanding.
Assuming the availability of the c++11 standard, you could extend the lifetime of the rvalue reference returned by the factory method instead of constructing a new object, like so:
MyClass A(1, 2);
MyClass&& B = MyClass::Clone(A);
Addendum:
As noted by patatahooligan in a comment below, there might be a copy constructor invocation in your version of MyClass::Clone itself. This can/needs to be rewritten to
MyClass MyClass::Clone(const MyClass& other) {
return {other, 10};
}
All of this will be completely unecessary once C++17 comes with mandatory copy-elision comes around, so you can look forward to that.
In C++03 it is still possible to achive the desired outcome albeit the solutions are longer and require more code. I offer two alternatives:
Instead of creating a set of static methods, I would advise for using tag-dispatch to signal different constructor meanings. This works by overloading the constructor with type arguments which have clear names but hold no data.
struct clone_tag {};
class MyClass {
public:
MyClass(int x, int y) : b_(x, y) {}
MyClass(const MyClass& self, clone_tag) : b_(self.b_, 10) {}
private:
NotMyClass b_;
};
int main() {
MyClass A(1, 2);
MyClass B(A, clone_tag());
}
Or this could even go as far as a genuine factory pattern, where the building the factory and final construction of the object is seperated. This clearly needs more time to set up but could improve code quality, depending on the actual number of different constructor paths.
class MyClass {
public:
struct Clone {
Clone(const MyClass& self) : self(self) {}
const MyClass& self;
};
struct Init {
Init(int x, int y) : x(x), y(y) {}
int x, y;
};
MyClass(Init i) : b_(i.x, i.y) {}
MyClass(Clone c) : b_(c.self.b_ , 10) {}
private:
MyClass(const MyClass& other, int c) : b_(other.b_, c) {}
NotMyClass b_;
};
int main() {
MyClass A = MyClass::Init(1, 2);
MyClass B = MyClass::Clone(A);
}
On second thought I realize that giving three different alternatives could cause more confusion than a short answer which is less precise. I therefore tried to list them in order of needed refactoring work although this is only guess work about your real code base.

Provide default value for constructor initializing reference instance variable

I have a class Foo that has a reference to Bar as an instance variable. Is there a way to have one of the Foo constructors create a Bar instance (so users of the Foo class need not explicitly create a Bar object themselves)?
I'd like to do something like this:
class Foo {
private:
int size;
Bar& bar; // shared by other objects
//several other instance variables
public:
Foo(int s, Bar& b) : size(s), bar(b) {}
// This, of course, doesn't work. I can't think of an
// alternative that doesn't require users of Foo to
// create Bar
Foo(int s) : size(s), bar(Bar(s)) {}
}
(In "real life", Foo and Bar are more complicated. Bar is a friend of Foo that has a private constructor used only by Foo. Each Bar can be shared by several Foos. This isn't a major problem, I'm just curious if it can be done without pointers.)
The simplest solution would be to allocate a Bar if and when you need one. Adding a member std::unique_ptr<Bar> is the easiest way of achieving this. I can't conceive of a system that addresses your problem that will not add some memory overhead to your class. You either have to account for storage for a Bar even when it isn't needed or you have to account for some state to track rather or not your Bar is internally managed. Using a unique_ptr adds the size of a pointer to your class.
#include <memory>
class Foo {
private:
std::unique_ptr<Bar> storage;
Bar& bar;
int size;
public:
Foo(int s, Bar& b) : bar(b), size(s) {}
Foo(int s) : storage(std::make_unique<Bar>(s)), bar(*storage), size(s) {}
};
A more elaborate solution could be to provide a wrapper for Bar that provides a default constructor that takes care of initializing a new instance.
Note that in your example, you do not initialize the member in the same order as they are declared. Members will be initialized in the order they are declared regardless of how you order them in your initialization list.
If every Foo shall have it's own individual Bar-object (not a single shared one), then you could try the following:
class Foo {
private:
Bar& bar; // shared by other objects
int size;
Bar* defaultBar;
//several other instance variables
public:
Foo(int s, Bar& b) : size(s), bar(b), defaultBar(nullptr) {}
Foo(int s) : Foo(s, *(new Bar(14))) { defaultBar=&bar; };
virtual ~Foo() {
if (defaultBar)
delete defaultBar;
}
void setBar(Bar &b) {
if (defaultBar)
delete defaultBar;
defaultBar = nullptr;
bar=b;
}
};
Hope it is what you are looking for.
// This, of course, doesn't work. I can't think of an
// alternative that doesn't require users of Foo to
// create Bar
Foo(int s) : size(s), bar(Bar(14)) {}
You can use a default Bar object, which can be a static member variable of Foo or any other class that can provide such an object.
E.g.
class Foo {
private:
Bar& bar; // shared by other objects
int size;
//several other instance variables
static Bar& getDefaultBar();
public:
Foo(int s, Bar& b) : size(s), bar(b) {}
Foo(int s) : size(s), bar(getDefaultBar()) {}
}
Bar& Foo::getDefaultBar()
{
static Bar b(24);
return b;
}