0
\$\begingroup\$

I have a custom template class Array<T>, and am currently implementing the assignment operator.

However, I've come across a design decision: When assigning two arrays, must I require them to have the same size, or adjust the LHS Array size to the RHS?


Pros and Cons of Requiring Equal Size

Pro

  • Most of the time, the array is often thought of as a static container (there are std::vector, etc. for dynamic containers).
  • It's more efficient, because I do not have to reallocate; rather, I overwrite each element of the LHS.

Con

  • It's a hassle for the user to do the work himself if he does intend to have a variable-length Array, plus it would be inefficient.

Code

Here are the implementations of the two versions I'm considering.

Equal Size

template <typename T>
bool JKArray<T>::operator=(const JKArray<T> & rhs)
{
    if (this == *rhs) { return true; }
    else
    {
        if (size_ != rhs.size_) { return false; }
        else
        {
            for (int i = 0; i < size_; i++) { array_[i] = rhs.array_[i]; }
        }
    }
}

Unequal Size

template <typename T>
void JKArray<T>::operator=(const JKArray<T> & rhs)
{
    if (this == *rhs) { }
    else
    {
        if (size_ != rhs.size_)
        {
            delete [] array_;
            size_ = rhs.size_;
            array_ = new T[size_];
        }
        for (int i = 0; i < size_; i++) { array_[i] = rhs.array_[i]; }
    }
}

Note

Regarding the implementation of Equal Size: I return false if the size is not equal, and true otherwise. Of course, I could use exceptions, however this is satisfactory for now.


I'm leaning towards making operator= fail* if RHS is a different length than LHS.

I'd like to know what other's have done, and what the prefer. To avoid closing this on the basis of opinion-based, please give explanations as to why one might be preferred over the other.


*See 'Note' under 'Code'.

\$\endgroup\$
  • \$\begingroup\$ On previous questions I've asked (mostly on SE), people have asked what the point is of reinventing the wheel. So, before anyone asks here: I'm doing this solely for academic purposes. \$\endgroup\$ – Fine Man Oct 31 '16 at 20:15
  • \$\begingroup\$ Unfortunately your question is off-topic as of now, as the code to be reviewed must be present in the question. Please add the code you want reviewed in your question. Thanks! \$\endgroup\$ – Edward Oct 31 '16 at 20:15
  • \$\begingroup\$ Personally I would require size to be part of the type. That way it will be compilation error. We already have std::vector<>. Otherwise it is not an array, it is surprising array at best. I'd expect sizeof(a) to be the same as for C array, but in your code it will probably be sizeof(T*). \$\endgroup\$ – Incomputable Nov 1 '16 at 0:00
  • 1
    \$\begingroup\$ This would be easier to evaluate with more context, e.g. the constructor(s) and member variables. \$\endgroup\$ – mdfst13 Nov 1 '16 at 1:56
  • \$\begingroup\$ "Here are the implementations of the two versions I'm considering." Hypothetical code is off-topic, please have a look at the help center. \$\endgroup\$ – Mast Nov 1 '16 at 11:42
2
\$\begingroup\$
template <typename T>
bool JKArray<T>::operator=(const JKArray<T> & rhs)

template <typename T>
void JKArray<T>::operator=(const JKArray<T> & rhs)

Neither of them have the correct signature for a copy assignment. The correct signature for that operator is class_name & class_name :: operator= ( const class_name & ).

That already rules out abusing the return value to signal success or failure. If you want runtime errors, you got to do it with exceptions.

If you violate that signature, it makes common patterns such as chained assignment (a = b = c = {...};) impossible. This may break other templated libraries depending on your datatypes to adhere to the standards.

Especially returning a bool may cause rather unexpected side effects.


Pro

  • Most of the time, the array is often thought of as a static container (there are std::vector, etc. for dynamic containers).
  • It's more efficient, because I do not have to reallocate; rather, I overwrite each element of the LHS.

Con

  • It's a hassle for the user to do the work himself if he does intend to have a variable-length Array, plus it would be inefficient.

That list is by no means complete. There are more aspects to cover:

  • Iterator invalidation

    When you have to re-allocate the backing data structure, it would be trivial to keep all existing iterators valid for same-size copies, while the same feat is problematic for different sizes.

  • Heap vs stack allocation

    For fixed size arrays, it's not even necessary to allocate them on the heap, sufficiently small arrays may as well be allocated straight on the stack. This is obviously impossible to do with the dynamically sized ones.

  • Array or Vector?

    While you call it Array, if you make it re-sizable it behaves more like an std::vector rather than an std::array.

  • Size as part of the type?

    As @Olzhas mentioned, it's an option to include the size of the backing array as a template parameter. This provides compile time checks for compatible or incompatible sizes. Whereby compile time checks - if applicable - are obviously to be preferred.

    This would actually be how std::array does it.

\$\endgroup\$
  • \$\begingroup\$ Very comprehensive answer; thanks, especially for mentioning the signature; I was aware of the unconventional return type because I'm not a fan of a=b=c;, but I didn't realize that it could affect other libraries. The reason of not implementing exceptions was that I haven't learned them yet (as mentioned previously, I'm learning C++), so I hacked a simple way to check the status of success. However, after reading your note on incorrect signatures, I'll just std::cerr instead (until I learn about exceptions). \$\endgroup\$ – Fine Man Nov 1 '16 at 19:53
  • \$\begingroup\$ Regarding "include the size of the backing array as a template parameter": I'm hesitant to implement this, because then I would not be able to implement operator+ (or, at least, I'm not aware of any other ways to do so), unless the user sums the sizes when declaring their instances (but then it's a hassle to the user, which I'd like to avoid). \$\endgroup\$ – Fine Man Nov 1 '16 at 20:18
  • \$\begingroup\$ @SirJony Of course you would be able to implement operator+, at least as long that's supposed to mean element wise addition. If that's supposed to mean concatenation though, no, then it won't be possible. That requires to use dynamic sizes to begin with. You got to decide what you need, define the requirements. Unless you know them, neither the fixed nor the dynamic sized container are inherently better. \$\endgroup\$ – Ext3h Nov 1 '16 at 20:26
  • \$\begingroup\$ Yes, sorry. I meant concatenation. I've implemented the class with dynamic allocation. The discussion is heading off-topic (not your fault, but it's not related to the Q anymore), so in case I need further clarification on designing my class, I'll post another question. Thanks! \$\endgroup\$ – Fine Man Nov 1 '16 at 20:29

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.