0
\$\begingroup\$

Initial stage of implementation of the polynomial class. Essentially I need to implement the basic mathematical operations. Right now I have implemented just addition. Was wondering if this is a good way to proceed. I also want to be sure if the move constructor would be called during the addition operation.

poly.h

/* Class for polynomials*/
/* Constant term is the first coefficient*/ 
/*To Do: 
1. Establish invariants

*/ 
#ifndef POLY_H
#define POLY_H
#include <vector>
#include <iostream>
#include <cstdlib>
#include <stdexcept>
template< typename T>
class Poly
{
 public:
 Poly() = default; // default constructor
 Poly(size_t s):sz{s}, coeff{}{}; // constructor assumes positive size
 Poly(std::initializer_list<T> lst):sz{lst.size()}, coeff{lst}{}; // constructor with initializer list
 Poly(Poly && ) = default; // Move
 Poly &  operator = (Poly && ) = default;
 Poly (Poly const & ) = default; // Copy
 Poly &  operator = (Poly const & ) = default;

 T & operator[](int i) {
      if(i < 0 || i >= sz)
     throw std::out_of_range{"Poly::operator[]"};
   return coeff[i];
 }

 size_t getSize() const{return sz;};

 Poly &  operator += (const Poly &b){
   size_t bsz = b.getSize();
   if(sz > bsz){
     for(int i = 0; i < bsz; ++i)
       coeff[i] += b[i];
   }
   else{
     int i = 0;
     for(; i < sz; ++i)
       coeff[i] += b[i];
     sz = bsz;
     for(; i < bsz; ++i)
       coeff.push_back(b[i]);
   }
   return *this;
 }
 const T & operator[](int i) const{
      if(i < 0 || i >= sz)
     throw std::out_of_range{"Poly::operator[]"};
   return coeff[i];
 }


 T * begin(){return & coeff[0];}
  T * end(){return & coeff[0] + sz;}

 ~Poly() = default;

 private:
 std::size_t sz;
 std::vector  <T>  coeff; // coeff is a vector of type T. It could be the class Poly itself
 };
template <typename T>
inline Poly < T >operator + (const Poly < T > & a,  const Poly < T > & b){
  Poly < T > res{a};
  res += b;
  return res;
}

#endif /* POLY_H*/ 

main.cpp (for test runs)

#include <iostream>
#include <vector>
#include "poly.h"
int main(int argc,  char **argv)
{
  std::vector  < double > a;
  a =  {1, 2, 3};

  Poly < double > poly1{2, 3, 4, 5};
  Poly < double > poly2{2, 3, 4};
  poly1 += poly2;
  for(auto & x: poly1)
    std::cout << x;

  poly1 = poly2 + poly2;
  for(auto & x: poly1)
    std::cout << x;

  return 0;
}
\$\endgroup\$
3
\$\begingroup\$

Overall, this looks good, but I think there are a few places it's still open to a little bit of improvement.

Consistency

You're somewhat inconsistent about the type you use for the size (and index into) a polynomial--you have a constructor that takes a size_t, but subscript operator that takes an int. I'd choose one type, and use it consistently. Lacking a really specific reason to do otherwise, I'd use size_t throughout (or see below, and use size_type instead--it's probably a better choice than either size_t or int).

This can also simplify your code a bit. For example, you subscript operator checks for an argument less than zero, but that simply can't exist with size_t.

Consider conforming to container interface

Right now, your Poly type can act quite a bit like a container (e.g., it has begin(), end(), and operator[] just like a container that supports random access iteration. It's fairly trivial to add a few more typedefs to make it a reasonably complete implementation:

typedef T value_type;
typedef T &reference;
typedef std::vector<T>::iterator iterator;
typedef std::vector<T>::const_iterator const_iterator;
typedef std::vector<T>::difference_type difference_type;
typedef std::vector<T>::size_type size_type;

The standard's requirements for containers are spelled out in §[container.requirements] (aka §23.2 in recent drafts), especially Tables 96, 97 and 98.

Use iterators where applicable

This is almost part of the previous point, but (I think) bears being pointed out separately.

Rather than defining begin() and end() to just return pointers, I'd define them to return iterators. I'd at least consider adding the rest of the normal rbegin(), cbegin(), crbegin() (and matching *end()) functions as well.

Use the right member functions

That leads directly to the point: once you've changed its argument to a size_t, your operator[] can be:

T & operator[](size_t i) {
    if (i >= sz)
        throw std::out_of_range{ "Poly::operator[]" };
    return coeff[i];
}

This, in turn, reduces to:

T & operator[](size_t i) {
    return coeff.at(i);
}

I'd also at least consider following the same path as vector, and providing a .at() that does bounds checking and an operator[] that doesn't.

Code arrangement

I'd rather see the two overloads of operator[] right next to each other in the code (without getSize() and operator+= between them). [Yes, this is a pretty minor point.]

Don't repeat yourself

Right now, you operator+= has two legs that start with nearly identical loops:

    if (...) {
        for (int i = 0; i < bsz; ++i)
            coeff[i] += b[i];
    }
    else {
        int i = 0;
        for (; i < sz; ++i)
            coeff[i] += b[i];

        // ...
    }

I'd rather eliminate that duplication. Personally, I'd probably do that by changing the order of operations a little bit. First, I'd resize my vector to the larger of the two sizes, then I'd do the addition:

coeff.resize(std::max(sz, bsz));
for (size_type i=0; i<sz; i++)
    coeffs[i] += b[i];

Alternatively, you could do something like this:

for (size_type i=0; i<sz; i++)
    coeffs[i] += b[i];
for (size_type i=sz; i<bsz; i++)
    coeffs.push_back(b[i]);

In this case, if sz >= bsz, the second loop won't execute (or: "will execute zero iterations"). To work with this, however, you also have to deal with the fact that it could index into a nonexistent element in the b. One way to deal with that would be to rewrite operator[] a bit to treat out of bounds accesses as legitimate, but with a zero coefficient:

T operator[](size_type i) { 
    if (i > sz)
       return T{};
    return coeffs[i];
}

So this is basically observing that a polynomial like \$A + bX^2\$ can be treated as \$A + bX^2 + 0x^3 + 0x^4 + ...\$. Obviously this is mathematically fine--since we're giving 0 as the coefficient for any higher order terms, they don't affect the overall value. This way, the right-hand operand of our += can be treated as always having at least as many terms as the left hand operand.

If wanted to get even fancier, we could do roughly the same with T &operator[] as well, but that gets quite a bit trickier, since it would (potentially) have to reallocate the vector of coefficients. At least in my opinion, that's probably more trouble than it's worth (but, I'll repeat: I'd prefer to resize first, and be done with it--it keeps everything simple and neat).

Move Constructor

As far as your question about use of the move constructor goes, the answer to precisely the question you've asked ("I also want to be sure if the move constructor would be called during the addition operation.") is "no".

The move constructor won't (at least normally be called during an addition operation. Rather, it'll be used after the addition operation, to move value from the temporary that's created in the addition, into the destination being initialized from that addition. If you're assigning to that destination rather than initializing it, the move assignment operator will be used instead.

As far as the question you really intended to ask though: yes, these will be used to move the result of an addition into a destination. This is fairly easy to verify by changing your implementation a bit to add some instrumentation:

Poly(Poly &&src) : sz(src.sz), coeff(std::move(src.coeff)) {
    std::cout << "\nMove ctor\n";
}
Poly &  operator = (Poly &&src) {
    sz = src.sz;
    coeff = std::move(src.coeff);
    std::cout << "\nMove assign\n";
    return *this;
}

The result when running your main.cpp is:

4685
Move ctor

Move assign
468

Showing that yes, the move ctor and move assignment operator were actually used.

\$\endgroup\$
  • \$\begingroup\$ 1. Can you give a reference where I can find the standards for the part of the answer "Consider conforming to collection interface"? I don't have much idea about it. 2. Can you say whether the move constructor would be invoked if a do a+b where a and b are objects of the said class? \$\endgroup\$ – Saurav Mar 29 '16 at 8:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.