c++big4: prefer synthesized

I think it’s the author of [[safe c++]] who pointed out that if we have to maintain non-default big4, then it’s extra workload for the maintenance programmer. He argued convincingly that it’s not a good idea to require other programmers or yourself to “always remember to do something”

pointer as field –#1 pattern in c++ explains that shared_ptr as a pointer field allows us to use the default big4.

Advertisements

c++q[new] variations

  1. variation: new MyClass(arg1) // most common. Can throw bad_alloc
  2. variation: new MyClass() // better form, calling the no-arg ctor
  3. variation: new MyClass //bare word, same as above. See op-new: allocate^construct #placement #IV
  4. variation: new (nothrow) MyClass(…) // returns NULL upon failure
  5. variation: placement new
  6. variation: array-new // no argument allowed!

c++ ctor calls(confusing), another summary

See more details in my post https://bintanvictor.wordpress.com/2013/08/12/c-no-arg-ctor-call-without-new-confusing/. Is there anything new
here? No, just another summary.

On heap (“new”) or with ctor args, things are more clear-cut and we make fewer mistakes. My confusions are the no-arg ctor calls on
the stack.

Animal a1; //ctor. Standard idiom but I would try to use the alternative form below.
Animal a2 = Animal(); // ctor – temp obj. This form is possibly less efficient, but this form is flexible —
Animal a3 = Animal(someArt)

// Above syntax may or may not create additional temp objects, in c++03 or c++11.

Animal aX(); // NOT ctor
throw Animal(); //ctor – temp obj
function6(Animal() );//ctor – temp obj

Rule of thumb — All the Animal() expressions create a temp.

c++ no-arg ctor Call +! q[new] #confusing

(Any QQ value? Yes some crazy interviewers may ask.)

With “new” or with ctor arguments, the confusion is lower. Note since no “new” in this entire article, all the instances are constructed on stack or global area.

Compare to java, the c++ default construction syntax looks confusing even inconsistent —

   Account* addr = &Account() //instantiates temp.. and saves its address. Not sure if this is meaningful.

Note  Account() is parsed differently by compiler, depending on context. In each case below, Account() looks like a function call returning an Account on stack–
   Account() ; // on a line by itself this expression simply creates a local temp instance on stack then …DISCARDED.
 
Account().print(); // instantiates temp, then invokes a method

Account myAcct = Account(); //(tested) unpopular but rather consistent with new(), and consistent with other languages. Arguably Recommended to beginners though inefficient. http://stackoverflow.com/questions/11480545/explicit-copy-constructor explains what it does – 

{create a temporary Account on the rhs; Implicitly call the copy constructor to copy from that temporary into myAcct.Marking the copy ctor “explicit” would break this code, which is recommended by many authors.

    ifstream in = ifstream(“aaa.txt”); //broken – due to deleted copy ctor
    ifstream in(“aaa.txt”); //tested. P96[[essentialC++]]

    Account arr[] = {Account(),Account()}; //2 ctor calls. Actually, You can even pass args to initialize the two array elements.

   func1( Account() ); //creates a temp and pass it to func1(). Trailing parens are necessary. I feel this is practical and common, but I seldom notice it.
   throw Account(); //creates a temp and.. See P1035 [[c++Primer]]
   count_if(itr1 , itr2 , MyPredicateFunctor()); //creates a temp … See P107 [[DanielDuffy]] + P86[[essentialC++]]
———

std::string const var; // no parentheses … allocates a const instance
class MyClass{/*empty class*/};
MyClass var3; // ok allocates the object behind the variable without initializing it
MyClass const var2; // can’t compile but why?
Answer — You need to add empty no-arg ctor into MyClass to avoid a POD class.
————- 
(See the code below.) I personally prefer the Account(…) syntax above, and avoid #5/#6 below, but #5/#6 are popular. We just have to memorize the inconsistent syntax about them. #6 is clear and unambiguous. #9 seems to follow the #6 syntax “pattern” but alas! The correct way to achieve the purpose is #5.

    Account myAcct; // #5 declares a variable and allocates an Account object. Popular idiom, but somewhat inconsistent with other syntax constructs like the (123) caseTrailing parens are problematic and will lead to #9:
    Account myAcct(); // #9 NOT a ctor call; declares a function! Anything parseable as a func declaration is parsed that way. See P34 [[effSTL]]

     Account myAcct(123); // #6 initialize the myAcct variable with a stack object,  not  discarded.


initializer in copier — bypass no-arg ctor

Say your copy ctor is invoked (often implicitly;-), and you need to clone a field of type T. If you don’t use the field initializer syntax, then the copier first calls T’s no-arg ctor to “make room” for the field, then calls T’s operator= to modify its state.

If you choose the initializer syntax, then T’s no-arg ctor is not needed. T’s copier is used instead. Chain reaction — Host copier invoking field copier

#include
using namespace std;
template class Safe{
public:
    Safe(const T& obj){ // T aObj will instantiate
        cout<<"Safe copier called"<<endl;
        cout<<"before assignment, "<<&aObj<<endl;
        aObj = obj;
        cout<<"after assignment, "<<&aObj<<endl;
    }
//    Safe(const T& obj):aObj(obj){} // T aObj won’t instantiate
    ~Safe(){    }
    T aObj;
};

struct Date{
    Date(){
        cout<<"Date no-arg called with this = "<<this<<endl;
    }
    Date(int y, int m, int d){}
    Date(const Date& rhs){
        cout<<"Date copier called with this = "<<this<<endl;
    }
};
int main() {
    Date d(1,2,3);
    cout<<"& d = "<<&d<<endl;
    Safe dSafe (d);
    cout<<"dSafe.aObj address = "<<&(dSafe.aObj)<<endl;
    return 0;
}

every STL container has a no-arg no-parenthesis ctor

I believe iterators are seldom (never?) instantiated with no-arg ctor, but  every container has a no-arg ctor.
Note all containers has a no-arg ctor, and you call it without parenthesis

#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include <list>
#include <deque>
#include <stack>
#include <queue>
#include <set> //supports multiset too
#include 

<map>
#include <ext/hash_set>
#include <ext/hash_map>
using namespace std;
using namespace __gnu_cxx;
// needed for hash_set

int main() {
    //    vector<float> cont;
    //    cout<<cont.size()<<endl;
    //    list<float> cont
    //    cout<<cont.size()<<endl;
    //    deque<float> cont;
    //    cout<<cont.size()<<endl;
    //    set<float> cont;
    //    cout<<cont.size()<<endl;
    //    multiset<float> cont;
    //    cout<<cont.size()<<endl;
    //    map<float, float> cont;
    //    cout<<cont.size()<<endl;
    multimap<float, float> cont;
    cout << cont.size() << endl;

    // container adaptors
    //    stack<float> adaptor;
    //    cout<<adaptor.size()<<endl;
    //    queue<float> adaptor;
    //    cout<<adaptor.size()<<endl;
    priority_queue<float> adaptor;
    cout << adaptor.size() << endl;

    //hashed
    hash_set<float> hashed;
    cout << hashed.size() << endl;
    //    hash_map<float, float> hashed;
    //    cout << hashed() << endl;
}

C++ array declaration and object construction

Pig * ptr = new Pig[5]; // pointer to an array of simple Pig objects
Q: Does this create 5 Pig objects? YES tested. P238[[ 24 ]]
Q: using no-arg constructor? yes tested.
Q: contiguous memory? yes

Note the return type of this or the singular-new is identical — ptr to Pig. No one knows to use array-delete except the guy initializing the ptr.

Pig arr[5];
Q: Does this create 5 empty Pig objects? … in contiguous memory? YES … on stack? ALWAYS!
Q: no-arg constructor needed? Yes.
Q: How many times does constructor get called if we use a debugger? 5 times

#include
#define SIZE 3
using namespace std;
#define S(x) cout<<#x<<":\t"<<x<<"\n";
class CAT {
public:
CAT() {
cout < no-arg constructor with addr = ” << this << "\n";
itsAge = 0;
itsWeight = 0;
} // default constructor
~CAT(); // destructor
int GetAge() const {
return itsAge;
}
int GetWeight() const {
return itsWeight;
}
void SetAge(int age) {
itsAge = age;
}
CAT operator=(const CAT&);
CAT::CAT(const CAT&);
private:
int itsAge, itsWeight;
};
CAT::CAT(const CAT& rhs) {
cout < copy constructor ” << this << " <- " << &rhs << "\n";
this->itsAge = rhs.itsAge;
this->itsWeight = 8888888;
cout << "<– copy constructor\n";
}
// if you return CAT& then you won’t call copy constructor upon return
CAT CAT::operator=(const CAT& rhs) {
cout < assigning ” << this << " <- " << &rhs << "\n";
if (this == &rhs)
return *this;
this->itsAge = rhs.itsAge;
this->itsWeight = 11111111;
cout << "<– assignment " << this << " <- " << &rhs << "\n";
return *this;
}
CAT::~CAT() {
cout << this << " Destructor called!\n";
this->itsAge = this->itsWeight = 999999999;
}
int main() {
CAT * Family = new CAT[SIZE];
cout << "<– array declaration\n";
int i;
CAT * pCat;
for (i = 0; i < SIZE; i++) {
pCat = new CAT; // same memory location would be reused after reclaim!
pCat->SetAge(2 * i + 1);
CAT & alias2family = Family[i];
CAT * tmp = &(Family[i] = *pCat);
S(tmp);// same addr across iterations, due to the assignment pbclone
delete pCat;
}
for (i = 0; i < SIZE; i++)
std::cout << "Cat #" << i + 1 << ": " << Family[i].GetAge()
<< std::endl;
delete[] Family;
//system(“pause”);
return 0;
}

local variable declaration IS allocation@@

– local nonref (i.e. stackVar) declaration always allocate-create the object — the C tradition. (java made a bold departure.) This is the big deal in this post.

MyClass c; // allocates without initializing. calls noArg ctor? I doubt it?
MyClass c= …. // ditto

– local ptr variable declaration — always allocates the 32 bits for the pointer. [1]
– local ref variable declaration — always allocates and initializes the reference. Compare to ptr.
– function param declaration (nonref) always allocate-create the object, on stack.
– A field declaration doesn’t allocate-create the field object — not until host class construction-time. Same as in java.
^ obviously, if you see new … then you know it calls constructor, new-expression is fundamentally different from nonref variable declarations because
^^ heap
^^ returns pointer
^^ object created is nameless. In contrast, nonref variable is a name-plate carved on a memory location.

[1] It may initialize it to 0 or seat it to a real pointee object

(badly) implicit ctor calls — var declaration

Q: When do c++ constructors get invoked? Harder to tell than in java.

MyClass a; // calls noarg, behaves exactly like int/float and other builtin types

a = MyClass(); // calls noarg, assignment, and destructor.
// New object created on stack then duplicated in a’s memory location, field by field. New object is then discarded as a STACK var.
// I believe the parentheses are compulsory after the class name.

MyClass arr[9]; // nine constructor calls. no-arg constructor needed! Note the position of [].

MyClass b(a); // calls copier ….. [2]

C c3 = c1; // calls copier, not assignment. same as [2]. Disabled by “explicit” keyword on the copier ctor

C c2;
c2 = c1; // calls assignment, not copier

////// So far, all the variables are nonref stack vars.

MyClass b(); // parsed as a function prototype, not a constructor call

MyClass * ptr; // confirmed no constructor call. The pointer is uninitialized.

MyClass * ptr = 0; // ptr starts off as uninitialized, then it is initialized to 0 i.e. null. You can see these 2 steps by debugging.

synthesized no-arg ctor

(Backgrounder — in both c++ and java, online tests and interviewers are crazy about nitty gritty of default ctor)

C++ standard — default constructor is a constructor that can be called with no arguments. –> Unlike java, this includes a constructor whose parameters all have default arguments [1]. By this definition, “default ctor” means something different from the synthesized no-arg (in multiple ways, but let’s not digress).

It’s best to avoid the ambiguous “default dtor” terminology in favor of “synthesized no-arg ctor”

In both java and c++, IF default ctor is needed in you program, you can create compilation errors by accidentally suppressing no-arg synthesization. As soon as you DECLARE/define any ctor in your class,
that synthesization is immediately suppressed.

* [1] Item 45 [[eff c++]] describes the default ctor as an no-arg.
* java also refers to it as the no-arg.