Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid indirect instantiation from private constructor through operation

I am trying to create a class whose objects must contain a short description ("name") of what their value represent. Therefore the only public constructor should take a string as argument.

For the operations, however, I need to create temporary (no relevant name) object to calculate the value to be assigned to an already existing object. For that I have implemented a private constructor, which should not be used, neither directly nor indirectly, to instantiate a new object - these temporary objects should only be assigned to an already existing object, through operator=, which only copies the value rather than name and value.

The problem comes with the use of "auto". If a new variable is declared as follows:

auto newObj = obj + obj;

the compiler deduces the return type of operator+ and directly assign its result to newObj. This results in an object with a irrelevant name, which should not be possible to instantiate.

Also, deducing the type of an already existing object should still be possible from some functions, like:

auto newObj = obj.makeNewObjWithSameTypeButOtherName("Other name");

Follows a code demonstrating the problem:

#include <iostream>
#include <string>

using namespace std;

template<class T>
class Sample
{
    public:
    Sample(const string&);

    Sample<T> makeNewObj(const string&);
    // Invalid constructors
    Sample();
    Sample(const Sample&);

    void operator=(const Sample&);
    void operator=(const T&);

    Sample<T> operator+(const Sample&) const;

    void show(void);

private:
// Private constructor used during operations
Sample(const T&);

T _value;
string _name;


};

template<class T>
Sample<T>::Sample(const string& name)
{
    this->_name = name;
    this->_value = 0;
}

template<class T>
Sample<T>::Sample(const T&value)
{
    this->_name = "Temporary variable";
    this->_value = value;
}

template<class T>
Sample<T>
Sample<T>::makeNewObj(const string& name)
{
    return Sample<T>(name);
}

template<class T>
void
Sample<T>::operator=(const Sample& si)
{
    this->_name = this->_name; // Make explicit: Never change the name
    this->_value = si._value;
}

template<class T>
void
Sample<T>::operator=(const T& value)
{
    this->_name = this->_name; // Make explicit: Never change the name
    this->_value = value;
}

template<class T>
Sample<T>
Sample<T>::operator+(const Sample& si) const
{
    // if any of the two values are invalid, throw some error
    return Sample<T>( this->_value + si._value );
}

template<class T>
void
Sample<T>::show(void)
{
    cout << _name << " = " << _value << endl;
}

int main()
{
    Sample<double> a("a"), b("b");
    a = 1; // Sample::operator=(const T&)
    b = 2.2; // Sample::operator=(const T&)
    a.show(); // Output: a = 1
    b.show(); // Output: b = 2.2

    auto c = a.makeNewObj("c"); // Should be possible
    c = a + b; // Sample::operator+(const Sample&) and Sample::operator=(const Sample&)
    c.show(); // Output: c = 3.2

//    Sample<double> d; // Compiler error as expected: undefined reference to `Sample::Sample()'
//    auto f = a; // Compiler error as expected: undefined reference to `Sample::Sample(Sample const&)'

    // This is what I want to avoid - should result in compiler error
    auto g = a+c; // No compiler error: uses the private constructor     Sample::Sample(const T&)
    g.show(); // Output: Temporary variable = 4.2  <-- !! Object with irrelevant name
}
like image 943
André Lucas Chinazzo Avatar asked Aug 17 '18 14:08

André Lucas Chinazzo


People also ask

Can we instantiate a class with private constructor?

Yes, we can access the private constructor or instantiate a class with private constructor. The java reflection API and the singleton design pattern has heavily utilized concept to access to private constructor.

What can you not do with a class with private constructor?

A private constructor in Java ensures that only one object is created at a time. It restricts the class instances within the declared class so that no class instance can be created outside the declared class.

What happens if we make constructor as private?

Yes, we can declare a constructor as private. If we declare a constructor as private we are not able to create an object of a class. We can use this private constructor in the Singleton Design Pattern.

How do you make a constructor object private?

We can declare a constructor private by using the private access specifier. Note that if a constructor is declared private, we are not able to create an object of the class. Instead, we can use this private constructor in Singleton Design Pattern.


1 Answers

A quick workaround is to not return a temporary Sample<T> from operator +. Since you only want the value part you can just return that instead. That changes the code to

T operator+(const Sample&) const;

template<class T>
T
Sample<T>::operator+(const Sample& si) const
{
    // if any of the two values are invalid, throw some error
    return  this->_value + si._value;
}

and then

auto g = a+c;

will make g whatever T is and g.show(); will not compile as g isn't a Sample<T>.

Sample<double> g = a+c;

Will also not work as it tries to construct g from a value and that constructor is private.


This will require adding

friend T operator+(T val, Sample<T> rhs) { return val + rhs._value; }

If you want to be able to chain additions like

a + a + a;
like image 165
NathanOliver Avatar answered Oct 15 '22 16:10

NathanOliver