7

I have the following problem:

I have a class which should do this:

Obj o;
Obj o1(o), o1=o; // deep-copies
const Obj c(o), c=o; // deep-copies
const Obj c1(c), c1=c; // shallow-copies
Obj o2(c), o2=c; // deep-copies

How can I do this preferably without inheritance? (I mean I would do Const_obj inheriting from Obj otherwise.)

EDIT:

Using o.clone() directly is not an option because then I could easily introduce bugs by accidentally not cloning.

EDIT:

Finally, there is a proper, complete solution with lazy evaluation using the idea from Effective C++ by Scott Meyers. Check out my answer below.

Barney Szabolcs
  • 11,846
  • 12
  • 66
  • 91
  • I think you might need to add a clone() method and do what you want explicitly. – Caribou Nov 12 '12 at 12:33
  • That is what I want to avoid not to write hardly trackable bugs into my code. It is soo easy that way. – Barney Szabolcs Nov 12 '12 at 12:34
  • Well I think silently doing it is far more dangerous - especially after you move jobs and someone new comes in... (My 2 penneth) – Caribou Nov 12 '12 at 12:38
  • 1
    You can make copy constructor and assign operator protected, so you will have to use either "deep copy" or "shallow copy" methods – Kamil Klimek Nov 12 '12 at 12:39
  • @Caribou: it is just a normal deep-copied class from the outside but speeded up under the hood. An implementation detail only. – Barney Szabolcs Nov 12 '12 at 12:52

3 Answers3

4

No, you can't.

  • A constructor can not be cv-qualified, so you can't force it to construct a const object.
  • The return type of a function (including operators) is not a part of it's signature, so you can't overload a function with just changing it's return type.

Also, if it was possible, I would find it really confusing. Just make methods that suit your needs, and name them in an unambiguous way.

SingerOfTheFall
  • 29,228
  • 8
  • 68
  • 105
2

After reading Effective C++ by Scott Meyers, the following is a solution:

define a template which does a lazy evaluation (with reference counting):

class Obj : private lazy<Obj_data>{};

and the lazy stores the Obj_data privately, has protected accessors, one for modification, one for read-only access.
The modifier accessor first deep-copies the Obj_data if necessary, then hands over the reference to the data. The read-only accessor just returns a const reference.

The overall cost of this is storing 2 extra pointers (one for the data and one for the counter) and a counter.

Implementation is something like this:

class lazy{
protected:
  lazy(const lazy&obj){lazy_copy(obj);}
  //(the required constructors, operator= ...)

  // accessors:
  const Obj_data& data() const {return *od;}
  Obj_data& mod_data() {make_private(); return *od;}
private:
  void lazy_copy(const lazy& obj);
  void make_private(); // this does the actual deep-copy, as late as possible.
private:
  counter*;
  Obj_data* od;
};

So, reading and modifying an attribute of Obj goes

void Obj::method(){
   cout << data().some_attribute;    // simple read
   mod_data().i = 10;                // simple modify
   const Obj_data& const_d = data(); // assignable for lots of read-outs
   Obj_data& var_d = mod_data();     // assignable for lots of modifications.
}

Note that you can only use data() in a const member as mod_data() is a non-const function in the class, so this solution is completely safe with little overhead.

Theory background: the desired behaviour in the question is an implementation detail, does not concern the client. Therefore we solve it by private inheritance.

Barney Szabolcs
  • 11,846
  • 12
  • 66
  • 91
0

You can, in part, with a dummy argument:

class C {
public:
    struct NonStandardCopy { };

    C (const C &) {
        // "ordinary" copy constructor with default behavior
    }

    C (const C &, NonStandardCopy) {
        // "other" "copy" constructor
    }
};

C c = c1; // default
C c (c1); // default
C c (c1, C::NonStandardCopy ()); // non-default

EDIT: A clone-only approach could be what you want (together with move semantics the performance hit might not be too large):

class C {
private:
    struct DeepCopy { };
    struct ShallowCopy { };

    C (const C &) = delete;

    C (const C &, DeepCopy) {
        // deep copy
    }

    C (const C &, ShallowCopy) {
        // shallow copy
    }
public:
    // move constructor
    C (C && other) = default;

    const C clone () const { // 1
        // shallow copy
        return C (*this, ShallowCopy ());
    }

    C cloneToNonConst () const {  // 2
        // deep copy
        return C (*this, DeepCopy ());
    }

    C clone () { // 3
        return cloneToNonConst ();
    }
};

C o;
C o1 = o.clone (); // call 3
const C o2 = o1.clone (); // call 3
const C o3 = o2.clone (); // call 1
C c4 = o3.cloneToNonConst (); // call 2; o3.clone () will give error
JohnB
  • 13,315
  • 4
  • 38
  • 65
  • +1, good thought, I am looking now for making it automatic, I'll get back here and write once I am ready with that using `Const_obj/Obj`. – Barney Szabolcs Nov 12 '12 at 12:43
  • Glad my answer is useful. I just understood that you want to distinguish between constructing a const and constructing a non-const. I think that is not possible within a constructor. You can, however, use public factory/clone functions (const/non-const returning, const vs. non-const member functions) and declare the constructors as private. – JohnB Nov 12 '12 at 12:47
  • Sorry, but it does not yet compile for me when I put the tests into a main function since the copyCtor is private. – Barney Szabolcs Nov 12 '12 at 13:10
  • Ah, you probably have to implement a public move constructor as well: `public: C (C && other) { // move field-by-field }` or, if your compiler supports it, `public: C (C && other) = default;` – JohnB Nov 12 '12 at 13:19
  • Thanks for the idea! My implementation based on it is in an answer. – Barney Szabolcs Nov 12 '12 at 14:41
  • Ah, finally I have found a proper implementation, check my new answer, I voted for deleting my old answer. – Barney Szabolcs Nov 14 '12 at 17:48