jiyi
jiyi

Reputation: 101

How to create the first object if only copy constructor is declared?

I saw a C++03 example online today.

class Cat {
    public:
        Cat(const Cat& iCat);
};

I was told that in this case, no default constructor will be automatically generated by compiler. If it is true, this means new Cat object can be created from an existing Cat object.

Can anyone tell me how to create the first Cat object in this case? or kindly correct me if my understanding is wrong.

Upvotes: 1

Views: 233

Answers (4)

Felix Glas
Felix Glas

Reputation: 15524

First of all, the valid answer should be that there is no way to create the first Cat. The example was probably only pulled out to demonstrate how a user-declared constructor will prevent the implicitly-declared default constructor to be declared.


Now, in spite of this, and purely if the means justifies the ends, there is, however, ways of creating the first cat using a layout-compatible object.

C++11 introduced layout compatibility:

Two standard-layout struct (Clause 9) types are layout-compatible if they have the same number of non-static data members and corresponding non-static data members (in declaration order) have layout-compatible types (3.9).


A standard-layout class is a class that:

  • has no non-static data members of type non-standard-layout class (or array of such types) or reference,
  • has no virtual functions (10.3) and no virtual base classes (10.1),
  • has the same access control (Clause 11) for all non-static data members,
  • has no non-standard-layout base classes,
  • either has no non-static data members in the most derived class and at most one base class with non-static data members, or has no base classes with non-static data members, and *has no base classes of the same type as the first non-static data member.

A standard-layout struct is a standard-layout class defined with the class-key struct or the class-key class.

This means that you can do:

class Cat {
public:
    Cat(const Cat& iCat) : x{iCat.x} {}
    int x;
};

class foo {
public:
    foo(int x) : m_x{x} {}
    int m_x;
};

int main() {
    foo f{5};
    Cat* c1 = reinterpret_cast<Cat*>(&f);
    Cat c2 = *c1;
    std::cout << c2.x << std::endl; // 5
}

The x and m_x members are used to demonstrate the copying of (layout-compatible) members.

Note: As mentioned in comment by @M.M, you might need to disable strict aliasing in your compiler for this to work.

Upvotes: 3

M.M
M.M

Reputation: 141554

Taking advantage of C++14 [class.mem]/18:

If a standard-layout union contains two or more standard-layout structs that share a common initial sequence, and if the standard-layout union object currently contains one of these standard-layout structs, it is permitted to inspect the common initial part of any of them. Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types and either neither member is a bit-field or both are bit-fields with the same width for a sequence of one or more initial members.

Cat is standard-layout, so we can make a union that contains two types of common initial sequence. Any two standard-layout classes with no data members meet the criteria for having a common initial sequence, so:

class Cat {
    public: Cat(const Cat& iCat);
};

class Dog {
    public: Dog();
};

union CatDog
{
    Dog dog;
    Cat cat;
};

int main()
{
    CatDog horse{};
    Cat cat(horse.cat);
}

Note: The standard does not precisely define the meaning of "inspect the common initial part". If the common initial part coincides with the entirety of the struct, does that mean the entire struct can be inspected as in my code? I guess that is a question for the language-lawyers.

Upvotes: 1

cwschmidt
cwschmidt

Reputation: 1204

Don't do the following in production code, but for the case of illustration, the following worked with "gcc 5.4.0".

As there is UB (undefined behaviour) involved, using std::string here e.g. as a member variable of cat, the example is stripped down to:

For the sake of bringing a 'cat' to life, you could do some reinterpret-cast to create your 'first' cat:

class Cat {
    public:
        Cat(const Cat& iCat) {
        }
};

int main() {        
    Cat* cat = reinterpret_cast<Cat*>(new char[sizeof(Cat)]);
    Cat cat2 = Cat(*cat);
    delete cat;
}

Again, the above code is just for illustration purpose, only worked on gcc 5.4.0 and does not guarantee that it works with other compilers.

There is the chance to easily introduce UB in the code, by expanding it as explained in the comments e.g:

#include <iostream>

class Cat {
    private:
        std::string name_;
    public:
        Cat(const Cat& iCat) {
           this->name_ = iCat.name_;
        }
        void setName(const std::string& name) { name_ = name; }
        const std::string& name() { return name_; }
};

int main() {        
    Cat* cat = reinterpret_cast<Cat*>(new char[sizeof(Cat)]);
    cat->setName("Lilly");
    Cat cat2 = Cat(*cat);
    std::cout << cat2.name() << std::endl;
    delete cat;
}

Upvotes: -2

πάντα ῥεῖ
πάντα ῥεῖ

Reputation: 1

Can anyone tell me how to create the first Cat object in this case? or kindly correct me if my understanding is wrong.

The only valid answer here is:

There is no way only using the code you posted. You probably missed some additional functionality that was given along with that example you saw.


There are ways though if any other constuctor is declared private, e.g.:

class Cat {
    public:
        Cat(const Cat& iCat);
        static Cat* CreateCat(const std::string& color) {
            return new Cat(color);
        }
    private:
        Cat(const std::string& color)
};

Upvotes: 5

Related Questions