2

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?

Community
  • 1
  • 1
Marius
  • 380
  • 3
  • 10
  • The language has a lot of rules associated with what functions get created automatically for classes. When you define a constructor with arguments, it modifies which functions get created by the compiler. In your case, you didn't get the default (empty) constructor because you defined one with arguments. Your code needed the default one (see the posted answer.) – ttemple Dec 16 '17 at 23:56
  • 1
    You really should try to create a [mcve], like [this one](https://www.ideone.com/OOdq5n). The issue has nothing to do with templates, queues, header files, etc. – PaulMcKenzie Dec 16 '17 at 23:57
  • 1
    https://stackoverflow.com/questions/4782757/rule-of-three-becomes-rule-of-five-with-c11#4782927 This explains that when you create a constructor, you usually need to create other functions (copy constructor, assignment, etc.) and why. You only created one of the suggested 3 or 5. – ttemple Dec 17 '17 at 00:01
  • 1
    @ttemple: The various Rules of N apply, among constructors, only to move and copy constructors. – Davis Herring Dec 17 '17 at 00:05
  • right you are. good reading though. – ttemple Dec 17 '17 at 00:16
  • Also, being that he is using 'new' in his constructor, he may want to evaluate the need for custom versions of the others... – ttemple Dec 17 '17 at 00:18
  • 1
    @DavisHerring if he has `new` in ctor, the he must surely have `delete` in the destructor. Thus the rule of 3/5 would apply. The idea is that a class should not own a resource unless that is the only functionality of the class (rule of 0). If that is the case (i.e. the class owns a resource) then it must implement all 3/5 (rule of 3/5). Usually the presence of any of the 3/5 is an good indicator that it owns a resource. But here the fact that a data member pointer is assigned to a dynamically allocated memory inside the default ctor is a good indication that the class owns this resource. – bolov Dec 17 '17 at 00:27
  • @DavisHerring I was not reacting to the question, I was reacting to your remark regarding the applicability of rule of N to the code in the OP. – bolov Dec 17 '17 at 00:32
  • @DavisHerring when you are wording it like this you are perfectly right. Maybe it was a communication issue. My apologies. – bolov Dec 17 '17 at 00:37
  • My first statement was flawed. Sorry... – ttemple Dec 17 '17 at 00:46

2 Answers2

5

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.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • Thanks for the explanation. I had to use Serial(unsigned int buffer_size) : _queue(buffer_size) for it to compile (no brackets) – Marius Dec 17 '17 at 00:02
  • 2
    @Marius: The `{}` is the constructor body. You do need one, but maybe you had two? – Davis Herring Dec 17 '17 at 00:04
1

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
Francis Cugler
  • 7,788
  • 2
  • 28
  • 59