Saturday 1 November 2014

C++: calling a constructor from within a constructor

Constructors within constructors? This is business as usual for the Java programmer, but what about C++? Unfortunately, it turns out that that's not so straightforward a task... In this post we are going to work this around with a simple example: a class which can be used to read data from text files. The key in this approach answers to the name of friend function! A friend function is a function which (i) is not a member function of a class, but (ii) has access to the private fields of objects that are passed to it as arguments.


Motivation

Although there are some ways to do this directly in C++11, in older versions calling a constructor from another constructor is (almost impossible). Some developers make use of tricks such as *this = MyClass(...), but this approach has certain disadvantages: it creates new instances for the object that is to be created. One shouldn't be surprised to get runtime errors when the destructor is called, and besides debugging is likely to become cumbersome. The approach presented here doesn't create any additional copies of the object which is to be created and is easy to implement.

Click on the image to magnify: The sign reads: "Due to festival rules, we are not allowed to sell drinks, including bottled water... Therefore..." and the second one goes: "August Peanut Sale: 1 Peanut for 1 Dollar! peanut shell included! (1 FREE Bottle of WATER comes with purchase of a peanut)." Well, that's the definition of a "workaround"!

Using private friends

Let's assume that our class comprises the following private fields:

private:
   istream * in;
   unsigned int data_count;
   float *data;

and we want to define the following constructors and the destructor:

public:
  StreamArrayReader(istream * in_stream);
  StreamArrayReader(char * filepath);
  virtual ~StreamArrayReader();

where the second one simply used the first one (and of course we don't want to duplicate the implementation of the former). Ideally, one would like to do something like:

StreamArrayReader::StreamArrayReader(istream * in_stream){
   // implementation
}

StreamArrayReader::StreamArrayReader(char * filepath) {
    ifstream instream;
    instream.open(filepath);
    StreamArrayReader(&instream);
    instream.close();
} 


Unfortunately, this is not allowed in C++. For that reason, we may define a private friend method as follows which implements what the first constructor is supposed to do:

private:
  friend void init_stream_array_reader(
      StreamArrayReader *o, istream * is);

Now this method has access to the private fields of o and the first constructor becomes:

StreamArrayReader::StreamArrayReader(istream * is) {
    init_stream_array_reader(this, is);
}

that is, its implementation has been moved to init_stream_array_reader. Note that this does not create multiple copies for the newly created copies. The second constructor becomes:

StreamArrayReader::StreamArrayReader(char * filepath) {
    ifstream instream;
    instream.open(filepath);
    init_stream_array_reader(this, &instream);
    instream.close();
}

that is, instead of having one constructor calling another, both call a private friend!
 


Example

And now we are going to put everything together and see how it works in practice. The implementation that will be presented here aims at reading data from text files where a single number is stored in each line of the file. The first line stores the number of the lines that follow.

 
The entries are treated as float, however a more general approach can be used using templates.

Here is the class definition:

/*
 * StreamArrayReader.h
 *
 *  Created on: Nov 1, 2014
 *  Author: Pantelis Sopasakis
 */

#ifndef STREAMARRAYREADER_H_
#define STREAMARRAYREADER_H_

#include <iostream>
#include <fstream>

using namespace std;

class StreamArrayReader {

public:
 StreamArrayReader(istream * in_stream);
 StreamArrayReader(char * filepath);
 virtual ~StreamArrayReader();

 float* getData() const {
  return data;
 }

 unsigned int getDataCount() const {
  return data_count;
 }

private:
 istream * in;
 unsigned int data_count;
 float *data;
 StreamArrayReader();
 friend void init_stream_array_reader(
          StreamArrayReader *o, istream * is);

};

#endif /* STREAMARRAYREADER_H_ */

and this is the implementation:

/*
 * StreamArrayReader.cpp
 *
 *  Created on: Nov 1, 2014
 *  Author: Pantelis Sopasakis
 */

#include "StreamArrayReader.h"
#include <string>

StreamArrayReader::StreamArrayReader() {
 data_count = 0;
 data = new float;
 in = NULL;
}

void init_stream_array_reader(StreamArrayReader *o, istream * is) {
 o->data_count = 0;
 o->in = is;
 string line;
 if (!o->in->eof()) {
  (*o->in) >> line;
  o->data_count = atoi(line.c_str());
  if (o->data_count == 0) {
   o->data = 0;
   return;
  }
 } else {
  return;
 }
 o->data = new float[o->data_count];
 unsigned int i = 0;
 while (!o->in->eof()) {
  (*o->in) >> line;
  o->data[i] = atof(line.c_str());
  i++;
 }
}

StreamArrayReader::StreamArrayReader(istream * is) {
 init_stream_array_reader(this, is);
}

StreamArrayReader::StreamArrayReader(char * filepath) {
 ifstream instream;
 instream.open(filepath);
 ifstream * ptr = &instream;
 init_stream_array_reader(this, ptr);
 instream.close();
}

StreamArrayReader::~StreamArrayReader() {
 delete[] data;  delete in;
}

it is now very easy to use this class from our main function:

int main() {
 ifstream instream;
 instream.open("data/example.txt");

 StreamArrayReader sar(&instream);
 float * data = sar.getData();
 unsigned int data_count = sar.getDataCount();

 cout << "count = " << data_count << endl;

 for (unsigned int i=0; i< data_count; i++){
  cout << "data[" << i << "] = " << data[i] << endl;
 }

 cout << "OK!\n";
 return 0;
}


which, it is equivalent to using the second constructor:

int main() {
 string filename = "data/example.txt";
 StreamArrayReader sar((char*)filename.c_str());
 float * data = sar.getData();
 unsigned int data_count = sar.getDataCount();

 cout << "count = " << data_count << endl;

 for (unsigned int i=0; i< data_count; i++){
  cout << "data[" << i << "] = " << data[i] << endl;
 }

 cout << "OK!\n";
 return 0;
}





No comments:

Post a Comment