3

I do not understand why the code below calls the base's class (=A) implementation of the

A::operator=(A&)

(indicated by output lines 15. and 17.) whenever the reference passed in as an argument are of the derived class type. Since all pointers point to objects of the derived class I would expect the same behaviour (=calling the operator from the derived class B) for all calls.

Output when executing the below listed code:

  1. A::Ctor objID=1280 text=BObject1
  2. B::Ctor objID=1280 text=BObject1
  3. A::Ctor objID=3279 text=BObject2
  4. B::Ctor objID=3279 text=BObject2
  5. --- ptrA->print():
  6. B::print() objID=1280 text=BObject1
  7. B::print() objID=3279 text=BObject2
  8. --- *ptrAObj1 = *ptrAObj2:
  9. B::operator=(A&) objID=1280 text=BObject1
  10. --- ptrAObj1->operator=(*ptrAObj2):
  11. B::operator=(A&) objID=1280 text=BObject2
  12. --- *ptrBObj1 = *ptrAObj2:
  13. B::operator=(A&) objID=1280 text=BObject2
  14. --- *ptrBObj1 = *ptrBObj2:
  15. A::operator=(A&) objID=1280 text=BObject2
  16. --- ptrBObj1->operator=(*ptrBObj2):
  17. A::operator=(A&) objID=1280 text=BObject2
  18. --- ptrAObj1->operator=(*ptrBObj2):
  19. B::operator=(A&) objID=1280 text=BObject2
  20. ---- TearDown()
  21. B::Dtor objID=1280 text=BObject2
  22. A::Dtor objID=1280 text=BObject2
  23. B::Dtor objID=3279 text=BObject2
  24. A::Dtor objID=3279 text=BObject2

Code:

#include "gtest/gtest.h"
#include <stdio.h>
#include <string>
#include <ctime>

class A
{
public:
  A(const std::string& sLabel=""):
    m_sText(sLabel)
  {
    std::srand(std::time(nullptr)); // use current time as seed for random generator
    int random_no = (std::rand()+(m_iObjectCounter*1999)) % 9999;
    m_iObjectID = random_no;
    m_iObjectCounter++;

    std::cout << "A::Ctor objID=" << m_iObjectID << " text=" << m_sText << std::endl;
   }

   virtual ~A()
   {
     std::cout << "A::Dtor objID=" << m_iObjectID << " text=" << m_sText << std::endl;
   }

   virtual void print() const
   {
     std::cout << "A::print() objID=" << m_iObjectID << " text=" << m_sText << std::endl;
   }

   virtual int operator=(A& rIn)
   {
     std::cout << "A::operator=(A&) objID=" << m_iObjectID << " text=" << m_sText << std::endl;
     m_sText = rIn.m_sText;
     return EXIT_SUCCESS;
   }

  std::string m_sText;
  int m_iObjectID;

  static int m_iObjectCounter;
};

int A::m_iObjectCounter=0;



class B : public A
{
public:
  B(const std::string& sLabel = "") :
    A(sLabel)
  {
    std::cout << "B::Ctor objID=" << m_iObjectID << " text=" << m_sText << std::endl;
  }

  virtual ~B()
  {
    std::cout << "B::Dtor objID=" << m_iObjectID << " text=" << m_sText << std::endl;
  }

  virtual void print() const
  {
    std::cout << "B::print() objID=" << m_iObjectID << " text=" << m_sText << std::endl;
  }

  virtual int operator=(A& rIn)
  {
    std::cout << "B::operator=(A&) objID=" << m_iObjectID << " text=" << m_sText << std::endl;
    m_sText = rIn.m_sText;
    return EXIT_SUCCESS;
  }
};



class TestAssignmentOperator : public ::testing::Test {
public:
  TestAssignmentOperator() :
    ptrAtoBObject1(NULL),
    ptrAtoBObject2(NULL)
  {
    ptrAtoBObject1 = new B("BObject1");
    ptrAtoBObject2 = new B("BObject2");
  }

  void SetUp(){}

  void TearDown() {
    std::cout << " ---- TearDown()" << std::endl;
    delete ptrAtoBObject1;
    delete ptrAtoBObject2;
  }

  ~TestAssignmentOperator(){}

  A* ptrAtoBObject1;
  A* ptrAtoBObject2;

};



TEST_F(TestAssignmentOperator, Test1)
{

  std::cout << " --- ptrA->print():" << std::endl;

  ptrAtoBObject1->print();
  ptrAtoBObject2->print();


  int iError = EXIT_FAILURE;
  std::cout << " --- *ptrAObj1 = *ptrAObj2:" << std::endl;
  iError = *ptrAtoBObject1 = *ptrAtoBObject2;


  std::cout << " --- ptrAObj1->operator=(*ptrAObj2):" << std::endl;
  iError = ptrAtoBObject1->operator=(*ptrAtoBObject2);


  std::cout << " --- *ptrBObj1 = *ptrAObj2:" << std::endl;
  B* ptrBToObject1 = dynamic_cast<B*>(ptrAtoBObject1);
  iError = *ptrBToObject1 = *ptrAtoBObject2;


  std::cout << " --- *ptrBObj1 = *ptrBObj2:" << std::endl;
  B* ptrBtoBObject2 = dynamic_cast<B*>(ptrAtoBObject2);
  *ptrBToObject1 = *ptrBtoBObject2;


  std::cout << " --- ptrBObj1->operator=(*ptrBObj2):" << std::endl;
  ptrBToObject1->operator=(*ptrBtoBObject2);


  std::cout << " --- ptrAObj1->operator=(*ptrBObj2):" << std::endl;
  ptrAtoBObject1->operator=(*ptrBtoBObject2);

}


int main(int argc, char **argv)
{

  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
DrFlow
  • 53
  • 1
  • 5
  • 4
    Can you make this a little more minimal? There's a lot to read, and I feel like your actual question/problem is much smaller. – AndyG Jan 11 '18 at 18:27
  • see also https://stackoverflow.com/questions/669818/virtual-assignment-operator-c – Zan Lynx Jan 11 '18 at 18:32
  • [OT]: `std::srand` only once. – Jarod42 Jan 11 '18 at 18:33
  • and see https://stackoverflow.com/questions/969232/why-does-virtual-assignment-behave-differently-than-other-virtual-functions-of-t?noredirect=1&lq=1 – Zan Lynx Jan 11 '18 at 18:34

2 Answers2

7

I believe it is because virtual int operator=(A& rIn) in class B is not the real default operator= so the compiler is creating a B& operator=(const B& other) for you and using that one.

To add to this, it is not enough to override your virtual operator=, you must also define the regular operator=.

And of course it is best to use C++11 features like the override keyword to guarantee that you actually wrote the function signature you meant to, instead of creating a new virtual function.

Zan Lynx
  • 53,022
  • 10
  • 79
  • 131
  • 1
    Good catch. And the default `operator=` calls a parent assignment operator when available, which is why `A::operator=` is getting called. – Silvio Mayolo Jan 11 '18 at 18:47
  • I agree with Silvio, good catch! The synthesized assignment operator in B is as-if it was calling `this->A::operator=(other)`, bypassing virtual function dispatch. I can see how one might think that it would have been `((A*)this)->operator=(other)`, and hence go through the virtual function table dispatch... but alas it is not so. – Eljay Jan 11 '18 at 19:35
1

In both cases, you call default B::operator=(const B&). Its default implementation call then A::operator=(A&)

Jarod42
  • 203,559
  • 14
  • 181
  • 302