Menu

Where to define class functions?

2008-08-18
2012-09-26
  • Chris Hammond

    Chris Hammond - 2008-08-18

    I'm using DEvC++4.9.9.2 on Windows XP.

    I'm doing some auxiliary projects to make sure I'm understanding things as I go. I tried to define a complex number class as well as the * operator on complex numbers. I did this in one file, and everything worked fine. Then I tried to break it up into 3 files: a header file defining the complex class, a source file that defines complex multiplication, and a main file where I simply use these things to multiply two complex numbers. This is where I run into trouble. I have a member function init() in the complex class which sets the number equal to (0,0). I think the problem is trying to define that. Here are all three files (they're short) as well as the compiler log. Thanks in advance.

    Chris

    Complex1.h

    ifndef COMPLEX1_H

    define COMPLEX1_H

    class complex {
    public:
    float Re;
    float Im;
    void init();

    };

    inline void complex::init();
    complex& operator * ( complex& w1, complex& w2);

    endif

    Complex1.cpp

    include <cstdlib>

    include <cmath>

    include <Complex1.h>

    using namespace std;

    inline void complex::init() //Constructor for a complex number
    {
    Re=0;
    Im=0;
    }

    complex& operator * ( complex& w1, complex& w2) // We multiply two complex numbers
    {
    complex w;
    w.Re=w1.Rew2.Re-w1.Imw2.Im;
    w.Im=w1.Rew2.Im+w2.Rew1.Im;
    return w;
    }

    ComplexNumMain2.cpp

    include <cstdlib>

    include <iostream>

    include<cmath>

    include<Complex1.h>

    using namespace std;

    int main(int argc, char *argv[])
    {
    complex z1;
    complex z2;
    complex w;

    z1.Re=1;
    z1.Im=2;

    z2.Re=3;
    z2.Im=4;

    w=z1*z2;

    system(&quot;PAUSE&quot;);
    return EXIT_SUCCESS;
    

    }

    Compiler Log

    Compiler: Default compiler
    Building Makefile: "C:\Dev-Cpp\Getting Started\ComplexNumbers2\Makefile.win"
    Executing make...
    make.exe -f "C:\Dev-Cpp\Getting Started\ComplexNumbers2\Makefile.win" all
    g++.exe -c Complex1.cpp -o Complex1.o -I"C:/Dev-Cpp/lib/gcc/mingw32/3.4.2/include" -I"C:/Dev-Cpp/include/c++/3.4.2/backward" -I"C:/Dev-Cpp/include/c++/3.4.2/mingw32" -I"C:/Dev-Cpp/include/c++/3.4.2" -I"C:/Dev-Cpp/include" -I"C:/Dev-Cpp/joshi"

    In file included from Complex1.cpp:3:
    C:/Dev-Cpp/include/Complex1.h:13: error: declaration of void complex::init()' outside of class is not definition Complex1.cpp: In functioncomplex& operator*(complex&, complex&)':
    Complex1.cpp:14: warning: reference to local variable `w' returned

    make.exe: *** [Complex1.o] Error 1

    Execution terminated

     
    • cpns

      cpns - 2008-08-18

      The operator() definitions earlier should have been declared void:

      void operator()( float r, float i) { Re=r; Im=r; }
      void operator()() { Re=0; Im=0; }

      Sorry.

       
    • Richard Kopcke

      Richard Kopcke - 2008-08-18

      first of all, #include <Complex1.h> should be #include "Complex1.h"

      Is there any special reason you put your constructor and operator declarations outside the scope of the class?

      Dick

       
    • cpns

      cpns - 2008-08-18

      Remove this line:

      inline void complex::init();

      from Complex1.h, or replace it with the definition in Complex1.cpp (and remove that definition). Or better yet place the body in the class definition thus:

      class complex {
      public:
      float Re;
      float Im;
      void init(){ Re=0; Im=0; }
      } ;

      which is implicitly inline. You would then need to remove all other definitions and declarations.

      The inline qualifier cannot work unless the definition appears in the same compilation unit as the usage, and for that to happen when you have multiple compilation units, the definition must be placed in the header. In practice inline is only a hint to the compiler, and unless you switch on optimisation, GCC ignores it in all cases, and just generates copies of the function in each compilation unit that are called in the normal way.

      Rather than use an init() function, it usually makes more sense to use a constructor, and in this case you can use an initializer list which is more efficient than member assignment:

      class complex {
      public:

      complex() : Re(0), Im(0) {}
      complex( float r, float i) : Re(r), Im(i) {}

      float Re;
      float Im;
      } ;

      With that definition, all instantiations will either be initialised with 0,0 or with arguments passed to the constructor. So your main() would look like this:

      int main()
      {
      complex z1(1, 2) ;
      complex z2(3, 4) ;
      complex w = z1 * z2 ;

      system("PAUSE");
      return EXIT_SUCCESS;
      }

      Moreover, it would make sense to have a constructor that takes a complex initilaiser:

      class complex {
      public:

      complex() : Re(0), Im(0) {}
      complex( float r, float i) : Re(r), Im(i) {}
      complex( complex i) : Re(i.Re), Im(i.Im) {}

      float Re;
      float Im;
      } ;

      Then you can write:

      complex z1(1, 2) ;
      complex z2(3, 4) ;
      complex w( z1 * z2 ) ;

      Further, if you do need to re-initilaise a complex object after it is instantiated you can define a functor thus:

      class complex {
      public:

      complex() : Re(0), Im(0) {}
      complex( float r, float i) : Re(r), Im(i) {}
      complex( complex i) : Re(i.Re), Im(i.Im) {}
      operator()( float r, float i) { Re=r; Im=r; }
      operator()() { Re=0; Im=0; }

      float Re;
      float Im;
      } ;

      Note that the second operator() is the same as your init() function except instead of invoking:

      w->init() ;

      you can merely write:

      w() ;

      I am not sure of the wisdom of this second implementation, since using the first and writing;

      w( 0, 0 ) ;

      is probably far clearer. You could even define:

      complex::operator()( float r = 0, float i = 0) { Re=r; Im=r; }

      and use either w() or w(0,0) with just the one definition.

      For ultimate flexibility, you might define complex as a class template so that it can be instantiated for other types such as double, long double and int for example without writing and maintaining special versions for each type.

      And of course much much more!

      Now all that said, you are aware are you that the C++ standard library includes a std::complex template class? http://www.dinkumware.com/manuals/?manual=compleat&page=complex.html. Even if you want to create your own as an academic exercise, you might consider using that as a model of good C++ design.

      Clifford

       
    • Chris Hammond

      Chris Hammond - 2008-08-18

      There doesn't seem to be a problem with using #include <Complex1.h>. I created a folder where I am putting all of my practice header files, and I added that to the places where the compiler looks for header files.

      "Is there any special reason you put your constructor and operator declarations outside the scope of the class?" Perhaps this was a really bad example, but I know that a class might have some rather complicated member function. I understand that one should not define functions in header files. So I wanted to see how to have a header file defining a class, where the member functions were defined in another source file.

       
      • cpns

        cpns - 2008-08-18

        You were doing fine, I think that response did more to confuse than clarify.

         
    • cpns

      cpns - 2008-08-18

      > first of all, #include <Complex1.h> should be #include "Complex1.h"

      Not at all. The error log does not indicate that the compiler failed to find <complex1.h>. You have to base your assertions on the facts before you, otherwise you will merely confuse people with erroneous and irrelevant advice.

      If he put it in say "C:/Dev-Cpp/joshi" which has been defined as an include path via a -I<path> argument, then <Complex1.h> is entirely correct. Given that that was my advice in response to an earlier question, I suspect that that is what he has done.

      > Is there any special reason you put your constructor and
      > operator declarations outside the scope of the class?

      1) He did not define a constructor. You cannot define a constructor outside of the scope of a class - what would it construct!?. His complex::init() was defined outside of the class definition, but not outside its scope. It was declared in the class definition, and by way of the complex:: scope resolution was still defined in the scope of the class.

      2) A binary operator may be defined by either a member function taking one argument or a non-member function taking two. It is largely a matter of preference, but IMO the latter is more intuitive. He is in good company; in Bjarne Stroutrup's "The C++ Programming Language", an example of operator overloading using a complex class is presented with operator+() defined as a non-member function. In fact he recommends, defining += as a member, and then + as a non-member defined in terms of += (and so it follows to define = as a member and then * in terms of =). This reduces code maintenance and ensures that say x = x * y is exactly equivalent to z *= y.

      Clifford

       
    • Chris Hammond

      Chris Hammond - 2008-08-18

      Thanks Clifford. By the way, I really appreciate all of your help. I wish I could return the favor. I'm finishing my PhD in math this year. On the off chance that you ever have a math question you'd like to discuss, feel free to email me at hammondc1 at gmail.com.

       

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.