2

I am having a strange issue with overloading the assignment operator in an inherited class. My code has the following relevant pieces

class BaseSignal
{
  ...
  virtual void Get(double* val) const {};
  virtual void Set(double val) {};
  BaseSignal& operator=(const BaseSignal& other);
  BaseSignal(const BaseSignal& orig);
  ...
}

BaseSignal& BaseSignal::operator=(const BaseSignal& other)
{
  double dval;
  other.Get(&dval);
  this->Set(dval);
}


template <class T>
class Net : public BaseSignal
{
  ...
  using BaseSignal::operator=;
  T* pval_;
  ...
  void Set(T val)
  {
    *pval_ = val;
  }
}

I don't think that the fact that the derived class is a template matters here.

The problem I'm having is when I evaluate the final equality in the sequence below:

Net<double>* net_real = new Net<double>(...);
*net_real = 1.0;
Net<double>* net_real2 = new Net<double>(...);
*net_real2 = 3.0;
*net_real = *net_real2;

Stepping through the code, all the virtual functions are followed, and indeed the value pointed to by pval_ of net_real gets updated to 3.0 when the Net<double>::Set method is called by the overloaded = operator. The problem is that when I return from the call stack to the next line after *net_real = *net_real2;, it looks like somehow a shallow copy of net_real2 was performed. Specifically, the pointer pval_ in net_real now suddenly has the same address as the pointer in net_real2.

Is somehow the overloaded base = operator called, THEN the implicit = operator of the derived class being called? I have not defined any equality/copy constructor of the derived class (but there is an explicitly-defined copy constructor of the base class).

EDIT

I've pasted a block of code below that shows what's going on. I stripped a lot of stuff out, but I hope this shows the problem. I just ran this in [cpp.sh].

// Example program
#include <iostream>
#include <string>

  class BaseSignal
  {
    public:
    /**
     * Constructor and destructor methods
     */
    // Nullor
    BaseSignal() {};
    // Destructor
    virtual ~BaseSignal() {};
    // Copy
    BaseSignal(const BaseSignal& orig);

    // Virtual settors
    virtual void Set(double val) {};

    // Virtual gettors
    virtual void Get(double* val) const {};

    // Equality to another signal
    BaseSignal& operator=(const BaseSignal& other);
    BaseSignal& operator=(const double& rhs);
  }; // class BaseSignal

  BaseSignal& BaseSignal::operator=(const double& rhs)
  {
    this->Set(rhs);
    return *this;
  }

  // Equality to another signal. Check vector widths are equal
  BaseSignal& BaseSignal::operator=(const BaseSignal& other)
  {
    std::cout << "******BaseSignal::operator= was called!******\n";
    double dval;
    other.Get(&dval);
    this->Set(dval);
    return *this;
  }

template <class T>
  class Net : public BaseSignal
  {
      public:
    T* pval_;
    /**
     * Constructors/destructors
     */
    Net() : BaseSignal()
    {
      // Initialize value
      pval_ = new T[1]{0};
    }
    ~Net() {delete[] pval_;}
    /**
     * Operator Overloads
     */
    using BaseSignal::operator=;

    /**
     * Setting with a constant value input just writes to the value at pval_.
     * If the Net is a 2-D vector, this method writes all values in the vector
     * to be the same val.
     */
    void Set(T val)
    {
        pval_[0] = val;
    } // Net<T>::Set

    /**
     * The Get method returns the number of elements based on the col_
     * parameter.
     */
    void Get(T* val) const
    {
        val[0] = pval_[0];
    } // Net<T>::Get
};

int main()
{
    double read_val;
    Net<double>* net_real = new Net<double>();
    *net_real = 1.0;
    net_real->Get(&read_val);
    std::cout << "net_real is equal to: " << read_val << "\n";
    std::cout << "net_real has pval_ pointer: " << net_real->pval_ << "\n";
    Net<double>* net_real2 = new Net<double>();
    *net_real2 = 2.0;
    net_real2->Get(&read_val);
    std::cout << "net_real2 is equal to: " << read_val << "\n";
    std::cout << "net_real2 has pval_ pointer: " << net_real2->pval_ << "\n";
    std::cout << "Now assigning value of net_real2 to net_real\n";
    *net_real = *net_real2;
    net_real->Get(&read_val);
    std::cout << "net_real is now equal to: " << read_val << "\n";
    std::cout << "net_real now has pval_ pointer: " << net_real->pval_ << "\n";
}

Colin Weltin-Wu
  • 347
  • 1
  • 2
  • 10
  • 3
    Unless you do something to stop it, the compiler will generate all copy/move operations for a class automatically for you. https://stackoverflow.com/questions/3734247/what-are-all-the-member-functions-created-by-compiler-for-a-class-does-that-hap – NathanOliver Aug 19 '19 at 21:02
  • 1
    Please [edit] your question so that it contains a [mre] that anyone can simply cut/paste, compile, and reproduce your observations. There could be many reasons for undefined or unexpected behavior. If you would like to get to the bottom of this, instead of people replying with random guesses, a [mre] is needed. I could probably take what's here, add some minimal code, run it, and get the expected results. If the problem is not apparent in the shown code, the problem must be in the code is not shown, so how can anyone be expected to figure out a problem in code that's not shown? – Sam Varshavchik Aug 19 '19 at 21:04
  • Your `BaseSignal::operator=` looks like it is missing a `return` at the end. – Eljay Aug 19 '19 at 21:06
  • 2
    Unrelated: Remember in C++ you do not have to `new` everything, and [it is generally best if you don't](https://stackoverflow.com/questions/6500313/why-should-c-programmers-minimize-use-of-new). – user4581301 Aug 19 '19 at 21:13
  • Hi @SamVarshavchik, I added the example, thanks for the suggestion. Please let me know if this is not "minimal" enough, I tried to strip out as much extra stuff as possible (to the point that it may look weird, i.e. arrays length 1, etc). – Colin Weltin-Wu Aug 19 '19 at 21:53
  • Thanks for providing a complete example that can be tested. After reproducing your results, I did some research and I believe that I have an answer, which I'll provide shortly. – Sam Varshavchik Aug 19 '19 at 22:04

1 Answers1

4

What's happening here is that

 using BaseSignal::operator=;

does not work the way you think it works. The usage of templates here confuses the issue somewhat, so I'll use a simpler example, with two ordinary classes.

class base {

     // Class declaration
};

class derived: public base {

     //

     using base::operator=;
};

The default assignment operators for both of these classes are:

 base &base::operator=(const base &);

 derived &derived::operator=(const derived &);

That's what the default assignment operators effectively are.

What the using declaration does, is effectively bring the base class's operator= into the derived class, as if the following operator was also declared:

 derived &derived::operator=(const base &);

But, of course, derived's default operator=(const derived &) is still there, nothing happened to it. So your code ends up invoking the default assignment operator in the derived class, anyway.

The C++ standard specifies the using declarations as follows:

[namespace.udecl]

A using-declaration introduces a name into the declarative region in which the using-declaration appears.

[formal grammar omitted]

The member name specified in a using-declaration is declared in the declarative region in which the using-declaration appears.

All that using does is effectively declare the

operator=(const base &);

from the base class in the derived class, in a manner of speaking.

Deleting the derived's default assignment operator won't help. The compiler will then complain about invoking a deleted operator.

The only realistic option here is to replace the using declaration with a wrapper that explicitly invokes the assignment operator from the base class.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Hi Sam, thank you for this detailed explanation. If what you say is true, then when I execute `*net_real = *net_real2;`, my "using" operator, which has the declaration `derived &derived::operator=(const base &);`, should never get called. Yet in the program output, I see `******BaseSignal::operator= was called!******`. Also, when I step through the code in the debugger, I also see that the "using" `=` operator is executed. So it almost seems like there is some sort of operator "chaining" happening, like virtual destructors. Am I missing something? – Colin Weltin-Wu Aug 19 '19 at 22:50
  • The default assignment operator for a Derived class calls the default assignment operator of the base class (and all class members). If you think about it, for a moment you will realize that. – Sam Varshavchik Aug 19 '19 at 22:52
  • Duh yeah ok. This clears it up for me, thank you so much! – Colin Weltin-Wu Aug 19 '19 at 22:54