Что является хорошей типизированной альтернативой вариативным функциям в C ++?

Вместе с этим вопросом . У меня возникли проблемы с поиском хорошего типобезопасного решения следующей, казалось бы, базовой проблемы. У меня есть класс music_playlist, в котором есть список песен, которые он должен проигрывать. Кажется, довольно просто, просто сделайте std :: list всех песен в очереди и сделайте его доступным для пользователя. Однако по необходимости декодирование звука и рендеринг звука происходит в отдельных потоках. Таким образом, список должен быть защищен мьютексом. Часто другие программисты, использующие мою библиотеку, забывали о мьютексе. Что, очевидно, привело к "странным" проблемам.

Итак, сначала я просто написал сеттеры для класса.

struct music{};
class music_playlist{
private:
     std::list playlist;
public:
     void add_song(music &song){playlist.push_back(song);}
     void clear(){playlist.clear();}
     void set_list(std::list &songs){playlist.assign(songs.begin(),songs.end());}
//etc
};

Это привело к пользовательскому коду, как показано ниже ...

music song1;
music song2;
music song3;
music song4;
music song5;
music song6;
music_playlist playlist1;
playlist1.add_song(song1);
playlist1.add_song(song2);
playlist1.add_song(song3);
playlist1.add_song(song4);
playlist1.add_song(song5);
playlist1.add_song(song6);

//or
music_playlist playlist2;
std::list songs;
songs.push_back(song1);
songs.push_back(song2);
songs.push_back(song3);
songs.push_back(song3);
songs.push_back(song5);
songs.push_back(song6);
playlist2.set_list(songs);

Хотя это работает и очень явно. Очень утомительно печатать, и это подвержено ошибкам из-за всей неразберихи, связанной с фактической работой. Чтобы продемонстрировать это, я на самом деле намеренно вставил ошибку в приведенный выше код, что-то вроде этого было бы легко сделать и, вероятно, не коснулось бы проверки кода, в то время как song4 никогда не воспроизводится в списке воспроизведения 2.

Оттуда я пошел, чтобы изучить вариативные функции.

struct music{};
class music_playlist{
private:
     std::list playlist;
public:
     void set_listA(music *first,...){
     //Not guaranteed to work, but usually does... bleh
         va_list Arguments;
    va_start(Arguments, first);
    if (first) {
        playlist.push_back(first);
    }
    while (first) {
        music * a = va_arg(Arguments, music*);
        if (a) {
            playlist.push_back(a);
        }else {
            break;
        }
    }
    }
    void set_listB(int count,music first,...){
         va_list Arguments;
     va_start(Arguments, first);
     playlist.push_back(first);

    while (--count) {
        music a = va_arg(Arguments, music);
            playlist.push_back(a);
    }
    }
//etc
}; 

Что привело бы к пользовательскому коду, как показано ниже ...

playlist1.set_listA(&song1,&song2,&song3,&song4,&song5,&song6,NULL);
//playlist1.set_listA(&song1,&song2,&song3,&song4,&song5,&song6); runtime error!!
//or
playlist2.set_listB(6,song1,song2,song3,song4,song5,song6);

Теперь гораздо легче увидеть, была ли песня добавлена ​​дважды или нет. Однако в решении A произойдет сбой, если NULL не находится в конце списка и не является кросс-платформенным. В решении B вы должны подсчитать количество аргументов, которые могут привести к нескольким ошибкам. Кроме того, ни одно из решений не является типобезопасным, и пользователь может передать несвязанный тип и дождаться сбоя во время выполнения. Это не казалось устойчивым решением. Тогда я наткнулся на std :: initializer_list. Не могу использовать его, некоторые из компиляторов, для которых я разрабатываю, еще не поддерживают его. Поэтому я попытался подражать этому. Я получил это решение ниже.

Считается ли такое использование оператора "," плохим тоном?

Что привело бы к тому, что код пользователя выглядел бы так ...

struct music_playlist{
    list queue;
//...
};
int main (int argc, const char * argv[])
{
    music_playlist playlist;
    music song1;
    music song2;
    music song3;
    music song4;
    playlist.queue = song1,song2; // The queue now contains song1 and song2
    playlist.queue+= song1,song3,song4; //The queue now contains two song1s and song2-4
    playlist.queue = song2; //the queue now only contains song2
    return 0;
}

Этот синтаксис не запутал нашу небольшую тестовую группу. Однако у меня были серьезные опасения по поводу злоупотребления перегрузкой операторов. Поэтому я разместил вопрос выше. Я хотел узнать, что думают программисты, которые были сравнительно экспертами нашей тестовой группы. Многим программистам это не понравилось, но казалось, что оно лучше, чем вышеперечисленные решения, потому что большинство ошибок будет обнаруживаться во время компиляции, а не только во время выполнения. Однако Том опубликовал интересный встречный пример, который мог вызвать неожиданное поведение.

//lets combine differnt playlists
new_playlist.queue =    song1        //the first playlist
                      ,(song3,song4) //the second playlist //opps, I didn't add song 3!
                      , song5;

Это разочаровывает меня в этом решении. Итак, у вас есть идеи по поводу лучшего решения?

P.S. Ни один из приведенных выше кодов не был скомпилирован, они используются только для примера.

6
задан Community 23 May 2017 в 12:04
поделиться