Marius
Marius

Reputation: 428

Compile fails without empty constructor

I new to c++ and trying to understand exactly what is happening with my code.

These classes are both defined in their own header files. The code is below.

Queue:

template<class T> class Queue
{
  public:
    Queue(unsigned int size)
    {
        _buffer = new T[size]; //need to make sure size is a power of 2
        _write = 0;
        _read = 0;
        _capacity = size;
    }
    
    /* other members ... */
   
   private:
    unsigned int _capacity;
    unsigned int _read;
    unsigned int _write;
    T *_buffer;
};

Serial:

template<class T> class Queue;
template<class T> class Serial
{
  public:
    Serial(unsigned int buffer_size)
    {
        _queue = Queue<T>(buffer_size); //<---here is the problem
    }
  private:
    Queue<T> _queue;
};

When I try to create an instance of Serial like this:

Serial<unsigned char> s = Serial<unsigned char>(123);

The compiler complains that there is no Queue constructor with zero arguments, at least that is what I think the errors means:

In instantiation of 'Serial<T>::Serial(unsigned int) [with T = unsigned char]':

no matching function for call to 'Queue<unsigned char>::Queue()'

ambiguous overload for 'operator=' in '((Serial<unsigned char>*)this)->Serial<unsigned char>::_queue = (operator new(16u), (((Queue<unsigned char>*)<anonymous>)->Queue<T>::Queue<unsigned char>(buffer_size), ((Queue<unsigned char>*)<anonymous>)))' (operand types are 'Queue<unsigned char>' and 'Queue<unsigned char>*')

invalid user-defined conversion from 'Queue<unsigned char>*' to 'const Queue<unsigned char>&' [-fpermissive]

invalid user-defined conversion from 'Queue<unsigned char>*' to 'Queue<unsigned char>&&' [-fpermissive]

conversion to non-const reference type 'class Queue<unsigned char>&&' from rvalue of type 'Queue<unsigned char>' [-fpermissive]

When I add an empty constructor to Queue it compiles with no problems. When I step through with the debugger I see it is going into the constructor with parameters, not the empty one.

Why is this happening?

Upvotes: 2

Views: 180

Answers (2)

Francis Cugler
Francis Cugler

Reputation: 7905

When looking at the OPs class and seeing an array of pointers and the use of using new in the CTOR personally I would design the classes in a manner similar to this:

Queue

#ifndef QUEUE_H
#define QUEUE_H

template<class T>
class Queue {
private:
    size_t size_;
    size_t capacity_;
    size_t read_;
    size_t write_;

    // Either of these depending on case of use or ownership
    std::vector<std::shared_ptr<T>> buffer_;
    std::vector<std::unique_ptr<T>> buffer_;
public:
    Queue() {}

    explicit Queue( size_t size ) : size_(size) {
        buffer_.resize( size_ );
    }   
}; // Queue

#endif // !QUEUE_H

Serial

#ifndef SERIAL_H
#define SERIAL_H

template<class T> class Queue;

template<class T> 
class Serial {
private:
    Queue<T> queue_;

public:
    Serial() {}
    explicit Serial( size_t bufferSize ) {
        queue_ = Queue<T>( bufferSize );
    }
}; // Serial

#endif // !SERIAL_H

The Serial Class is not much of an issue since it is using a stack object of a Queue type. Now as for the Queue object, since it is using pointers with the new operator there must also be a matching delete operator which is a direct indication that the destructor will have to manage some kind of memory resource(s).

It is due to this that I chose to use a vector<smart_ptr> instead of array[size] raw pointers. This helps to prevent memory leaks, dangling pointers etc. It is also important to write out at the least these 3 to complete the Rule of 3:

  • Destructor
  • Copy Constructor
  • Assignment Operator

And it may be necessary to write these to complete the Rule of 5:

  • Move Constructor
  • Move Assignment Operator

Upvotes: 1

HolyBlackCat
HolyBlackCat

Reputation: 96166

Serial(unsigned int buffer_size) lacks member init list, thus _queue has to be created with the default constructor first, and then a value (Queue<T>(buffer_size)) is assigned to it.

This:

Serial(unsigned int buffer_size) : _queue(buffer_size) {}

would use the Queue(unsigned int) constructor instead and would've worked without the default constructor.

Upvotes: 5

Related Questions