2

I'm totally new here, so I'm not very familiar with style of writing here, so sorry if the question doesn't look like it should. My question is, how can I create an array of object, but not with default constructors? If I have something like this:

set<movie> a(3);
set<movie> b(2);

And constructors: For movie

movie::movie()
{
    this->naziv=0;
    this->reditelj=0;
    this->trajanje=0;
}

movie::movie(char *name, char *red, int len)
{
    this->naziv=new char[strlen(name)+1];
    strcpy(naziv,name);
    this->reditelj=new char[strlen(red)+1];
    strcpy(reditelj,red);
    this->trajanje=len;
}

And for set:

template<class t>
set<t>::set()
{
    this->br=0;
    this->niz=0;
}

set<t>::set(int b)
{
    this->br=b;
    this->niz=new t[br];
}

Answers are great,but on course they teach us some basic stuff about c++,how to create class,template class,I mean,to write programs from the beginning,so for now we don't use that classes and functions that most of you mentioned. The assignment is to write the code this way,so how can I do that? The assignment is to make a class and a template class,template class is actually an array of objects,so I should make an object,that's an array of objects,and some other functions.

Here's my whole code:

Set.h

#pragma once
#include<iostream>
using namespace std;

template<class t>
class set
{
    int br;
    t* niz;
public:
    set();
    set(int b);
    ~set();
    set(set& copy);
    int vrati_br_elem()
    {
        return br;
    }
    bool pripada(t elem);
    set operator*(set& drugi);
    friend istream& operator>> <>(istream& ulaz,set<t> &s);
    friend ostream& operator<< <>(ostream& izlaz,set<t> &s);
};

template<class t>
set<t>::set()
{
    this->br=0;
    this->niz=0;
}

template<class t>
set<t>::set(int b)
{
    this->br=b;
    this->niz=new t[br];
}

template<class t>
set<t>::~set()
{
    if(this->niz!=0)
        delete [] niz;
}

template<class t>
bool set<t>::pripada(t elem)
{
    for(int i=0;i<this->br;i++)
        if(this->niz[i]=elem)
            return true;
    return false;
}

template<class t>
set<t> set<t>::operator *(set<t> &drugi)
{
    int broj=0;
    set<t> pom((this->br>drugi.br)?this->br:drugi.br);
    for(int i=0;i<this->br;i++)
        for(int j=0;j<drugi.br;j++)
            if(this->niz[i]==drugi.niz[j])
                pom.niz[broj++]=this->niz[i];
    pom.br=broj;
    return pom;
}

template<class t>
istream& operator>>(istream& ulaz,set<t> &s)
{
    for(int i=0;i<s.br;i++)
        cin>>s.niz[i];
    return ulaz;
}

template<class t>
ostream& operator<<(ostream& izlaz,set<t> &s)
{
    for(int i=0;i<s.br;i++)
        cout<<endl<<s.niz[i]<<endl;
    return izlaz;
}

template<class t>
set<t>::set(set<t> &copy)
{
    this->br=copy.br;
    this->niz=new t[br];
    for(int i=0;i<this->br;i++)
        this->niz[i]=copy.niz[i];
}

movie.h

#include<iostream>
using namespace std;

class movie
{
    char* naziv;
    char* reditelj;
    int trajanje;
public:
    movie();
    ~movie();
    movie(movie& copy);
    movie(char* name,char* red,int len);
    movie& operator=(movie& film);
    bool operator==(movie& film);
    friend istream& operator>>(istream& ulaz,movie& film);
    friend ostream& operator<<(ostream& izlaz,movie& film);
};

movie.cpp

#include"movie.h"
using namespace std;

movie::movie()
{
    this->naziv=0;
    this->reditelj=0;
    this->trajanje=0;
}

movie::~movie()
{
    if(naziv!=0&&reditelj!=0)
    {
        delete [] naziv;
        delete [] reditelj;
    }
}

movie::movie(movie &copy)
{
    this->naziv=new char[strlen(copy.naziv)+1];
    strcpy(this->naziv,copy.naziv);
    this->reditelj=new char[strlen(copy.reditelj)+1];
    strcpy(this->reditelj,copy.reditelj);
    this->trajanje=copy.trajanje;
}

movie& movie::operator =(movie &film)
{
    if(this!=&film)
    {
        delete [] naziv;
        delete [] reditelj;

        this->naziv=new char[strlen(film.naziv)+1];
        strcpy(this->naziv,film.naziv);
        this->reditelj=new char[strlen(film.reditelj)+1];
        strcpy(this->reditelj,film.reditelj);
        this->trajanje=film.trajanje;
    }
    return *this; 
}

bool movie::operator ==(movie &film)
{
    if(!strcmp(this->naziv,film.naziv)&&!strcmp(this->reditelj,film.reditelj)&&this->trajanje==film.trajanje)
        return true;
    return false;
}

istream& operator>>(istream& ulaz,movie& film)
{
    ulaz>>film.naziv>>film.reditelj>>film.trajanje;
    return ulaz;
}

ostream& operator<<(ostream& izlaz,movie& film)
{
    izlaz<<endl<<film.naziv<<endl<<film.reditelj<<endl<<film.trajanje<<endl;
    return izlaz;
}

movie::movie(char *name, char *red, int len)
{
    this->naziv=new char[strlen(name)+1];
    strcpy(naziv,name);
    this->reditelj=new char[strlen(red)+1];
    strcpy(reditelj,red);
    this->trajanje=len;
}
ildjarn
  • 62,044
  • 9
  • 127
  • 211
Kobe-Wan Kenobi
  • 3,694
  • 2
  • 40
  • 67
  • `std::allocator`, maybe with `std::uninitialized_copy` or `std::uninitialized_fill`. – Mooing Duck Jan 04 '12 at 22:43
  • 2
    Also, don't name your class `set`, or you'll confuse everyone. `std::set` is more known than whatever you are doing. Also, welcome to stack overflow. – Mooing Duck Jan 04 '12 at 22:43
  • 1
    Did you try to make the constructor private? Btw. your question is well formatted :-) – rekire Jan 04 '12 at 22:44
  • What are you trying to do? Do you want to delay the constructions of the objects and construct them later, or do you want to create an array, but construct the values calling a different constructor? – Grizzly Jan 04 '12 at 22:48
  • I suspect the error is in `this->niz = new movie[br]` (when t=movie). When you compile this, there will be an error because there is no default `movie()` constructor. Is this the question, @breakpoint ? – Aaron McDaid Jan 04 '12 at 23:18
  • @AaronMcDaid Yes,there's the problem,default constructor sets all to 0,and than when i try: set a(3); set b(2); cin>>a; cin>>b; cout< – Kobe-Wan Kenobi Jan 04 '12 at 23:26
  • (You should rename your set to avoid confusion with the `std :: set` class already built into C++) That's a lot of code in your question! What happens if you just use one my_set? `my_set a(3); cin>>a; cout << "got here"; cout< – Aaron McDaid Jan 04 '12 at 23:37
  • 1
    This looks like a [Rule of Three](http://stackoverflow.com/q/4172722/636019) violation to me.. Where is `set<>`'s copy-assignment operator? – ildjarn Jan 04 '12 at 23:51
  • Like Mooing Duck said, creating a type with name `set` is not exactly recommended due to possible confusion with `std::set`. Neither is `using namespace std;` in headers. But please, please don't combine those two dubious practices to define your own `set` in the global namespace and pull `std::set` into that two. thats just begging for problems, if someone using your code accidentaly includes `` instead of `"set.h"` – Grizzly Jan 05 '12 at 02:00

6 Answers6

2

Don't use built-in arrays, especially if you are new. Built-in arrays are best left to experts and even then they are often best avoided. Instead of using T[n] just use std::vector<T>. This one will start out empty an you can then e.g. push_back() the objects you are interested in.

That said, I don't see where you code excerpt actually has a problem.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
1

You can create an array of objects invoking the constructor directly.

movie objs[2] = {movie(arg1, arg2, arg3), movie(arg1, arg2, arg3)};
1

The standard way to do this is to use a allocator object, like all the standard containers.

template<class T, class alloc_type =std::allocator<T> >
class set {
     typedef alloc_type::pointer pointer; //bring in it's pointer type
     alloc_type alloc;

And then, use that for everything:

pointer buffer = alloc.allocate(100);
alloc.construct(buffer+0); //deault construct T
alloc.construct(buffer+1, T()); //construct T from copy
alloc.construct(buffer+2, 17); //construct T from 17

alloc.destroy(buffer+2);  //clean up objects
alloc.destroy(buffer+1); 
alloc.destroy(buffer+0); 
alloc.deallocate(buffer); //clean up buffer

Remember, it's standard to construct from lowest index to highest, and to destroy in the reverse order.

The "correct" way to do this has changed with C++11, but since I use MSVC10, which can't do the correct way, I still use this way.

Basic implementations of each of these functions is rediculously simple, though.

template<class T>
class myallocator {
public:
    typedef T value_type;
    typedef T* pointer;
    typedef T& reference;
    typedef const T* const_pointer;
    typedef const T& const_reference;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;

    myallocator () throw() {}
    template <class U> myallocator (const myallocator<U>&) throw() {}
    pointer address (reference x) const {return &x;}
    const_pointer address (const_reference x) const {return &x;}
    size_type max_size() const throw() {return size_type(-1);}

pointer allocate(size_type c,const_pointer h=0){return(T*)new char[sizeof(T)*c];}
    void deallocate(pointer ptr, size_type c) {delete [] ptr;}
    pointer construct(pointer ptr) {return new(ptr)T;}
    template<class U>
    pointer construct(pointer ptr, const U& from) {return new(ptr)T(from);}
    void destroy(pointer ptr) {ptr->~T();}
};

The two construct members use what is called "placement new" which creates the object in an already existing space. Here, an array of chars.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
0

You are trying to write your own container class, and you should not call it set. I will assume that you are calling it my_set in this answer.

I think this is the line you are interested in. You wish to pass in a default value here:

my_set<t>::my_set(int b, t default_value)
//creates a my_set with 'b' elements, where each element is set to default_value

Is this your goal? If so, it's easier to define niz as a *vector<t> instead of as t*

template<class t>
struct my_set {
    int br;
    vector<t> *niz;
    my_set(int b, const t& default_value);
}

template<class t>
my_set<t>::my_set(int b, const t& default_value) {
//creates a my_set with 'b' elements, where each element is set to 0
    this->br=b;
    this->niz=new vector<t>(b, default_value);
}

There are other changes you may consider, such as defining niz simply as vector<t>, not as vector<t>*, but I think that's beyond the scope of you original question.

Finally, I have a question of my own for everybody. How can I do an uninitialized array new[] in C++? I'd like to new an array of known size, but with unconstructed data, and then use something like uninitialized_copy to copy data in.

Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
0

The expression new T[n] will always allocate space for n T objects and call the T constructor on each element. Similarly, delete[] niz, will always call the T destructor on each element. It seems that you want to manually control when the T constructor and destructor are called, so rather than using ::operator new[], you could just use ::operator new and its placement syntax.

You want niz to be an array of br T objects. Instead of this:

niz = new T[br];

You can use this:

niz = static_cast<T *>(::operator new(br * (sizeof (T))));

which will allocate space in the heap for br contiguous T objects, but not call the T constructor on any of them. It's basically like using malloc() to allocate space for the T objects.

But, now you have a problem: how do you actually use one of the T objects? Before you can do anything with niz[i], you need to make sure that the ith T object has been constructed. You can use placement new to construct it:

new(niz + i) T();

Notice that niz + i is the pointer to the ith T object. The effect of this statement is that the T constructor is called in place using the space at reinterpret_cast<char *>(niz + i) through reinterpret_cast<char *>(niz + i) + (sizeof (T)).

Now you have another problem: how do you keep track of which T objects have been constructed? You need to know this in order to call the destructor on the ones that have been constructed, or else you might leak memory.

One solution is to use a std::vector<bool> having br bool objects. If the ith bool is true, then you will know that the ith T object was constructed. Otherwise, it needs to be constructed.

In the set<T> destructor, you need to make sure to destroy all T objects that have been constructed. Suppose that the ith T object has been constructed. To call the T destructor on the ith T object, you can use this statement:

(niz + i)->~T();

Putting it all together, you would get something like this:

#include <cstddef>
#include <iostream>
#include <new>
#include <vector>

template <typename T>
class set
{
    std::size_t br;
    T *niz;
    std::vector<bool> constructed;

public:
    std::string name;

    set()
        : br(0), niz(NULL), constructed()
    {
    }

    set(std::size_t br)
        : br(br), niz(NULL), constructed(br, false)
    {
        niz = static_cast<T *>(::operator new(br * (sizeof (T))));
    }

    void destroy()
    {
        std::cout << "~set(" << name << ")\n";

        if (niz) {
            std::vector<bool>::const_iterator begin = constructed.begin(), it, end = constructed.end();
            for (it = constructed.begin(); it != end; ++it) {
                if (*it) {
                    (niz + (it - begin))->~T();
                }
            }
            ::operator delete(niz);
        }
    }

    ~set()
    {
        destroy();
    }

    set<T>& operator=(const set<T>& other)
    {
        if (this != &other) {
            destroy();
            niz = NULL;

            constructed = std::vector<bool>(other.br, false);
            br = other.br;
            T *tmp = static_cast<T *>(::operator new(other.br * (sizeof (T))));
            try {
                std::size_t i;
                for (i = 0; i < other.br; ++i) {
                    if (other.constructed[i]) {
                        new(tmp + i) T(other.niz[i]);
                        constructed[i] = true;
                    }
                }
            } catch (...) {
                niz = tmp;
                destroy();
                throw;
            }
            niz = tmp;
            name = other.name + " (2)";
        }
        return *this;
    }

    T& operator[](std::size_t i)
    {
        if (niz && !constructed[i]) {
            new(niz + i) T();
            constructed[i] = true;
        }
        return niz[i];
    }
};

struct my_struct
{
    char c;

    my_struct(char c = 'a')
        : c(c)
    {
        std::cout << "my_struct('" << c << "')\n";
    }

    ~my_struct()
    {
        std::cout << "~my_struct('" << c << "')\n";
    }
};

int main()
{
    ::set<char> a, a2(3);
    a.name = "a";
    a2.name = "a2";

    {
        ::set<my_struct> b(3);
        b.name = "b";
        b[0].c = '1';
        b[2].c = '3';
        b[1].c = '2';

        ::set<my_struct> b2(4);
        b2.name = "b2";
        b = b2; b.name += ", going by the name 'b'";

        b[0].c = 'A';
        b2[1].c = 'B';
    }
}

Note: I do not recommend actually using this code. The point is to learn about the placement new operator and explicitly invoking a destructor through a pointer.

See STL templates vector and set for standard alternatives.

Daniel Trebbien
  • 38,421
  • 18
  • 121
  • 193
0

One of the problems in your code is that this will fail if either string is 0

ostream& operator<<(ostream& izlaz,movie& film)
{
    izlaz
        << endl << film.naziv // fails if film.naziv == 0
        << endl << film.reditelj  // fails if film.reditelj == 0
        << endl << film.trajanje << endl;
    return izlaz;
}

That crashes for me. You should not do cout << (char*)0;. It's better to do something like cout << "". You could fix it by changing the constructor of movie:

movie::movie()
{
    this->naziv="";    // an empty, non-null, string
    this->reditelj=""; // an empty, non-null, string
    this->trajanje=0;
}

But a better solution is to stop using char * and use std :: string instead.

Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88