Pertanyaan Mengembalikan tugas sementara di C ++


Dalam konteks pengunjung, saya perlu mengatur sementara variabel sebelum mengunjungi anak-anak, dan mengembalikan variabel itu sesudahnya. Saya menggunakan kode berikut, tetapi saya yakin ada cara yang lebih elegan dan tepat untuk melakukan ini:

template <typename TYPE> class TemporaryAssignment {
protected:
    TYPE& mVariable;
    TYPE mOriginalValue;
public:
    TemporaryAssignment(TYPE& inVariable, TYPE inValue) 
        : mVariable(inVariable), mOriginalValue(inVariable) {
        mVariable = inValue;
    }
    ~TemporaryAssignment(void) {
        mVariable = mOriginalValue;
    }
};

Ini memungkinkan saya untuk menulis sesuatu seperti berikut:

{
    ...
    TemporaryAssignment<string> t(myVariable, myTemporaryValue);
    visitChildren();
    ...
}
// previous value of myVariable is restored

Variabel akan kembali ke nilai sebelumnya ketika objek penugasan sementara keluar dari ruang lingkup. Apa cara yang lebih baik untuk melakukan ini?


4
2017-11-14 17:09


asal


Jawaban:


Tampak baik bagi saya kecuali bahwa destructor dapat melempar, yang buruk. swap dengan nilai asli alih-alih menetapkan (edit: ini berhubungan dengan std::string, tetapi lihat komentar untuk kemungkinan masalah dengan kelas yang kurang ramah pengguna daripada string).

Jika Anda mundur sedikit dari bagian kode ini, mungkin Anda dapat menemukan cara untuk tidak perlu menetapkan nilai sementara sama sekali. Status dapat berubah bersama dalam suatu objek dapat berakibat buruk untuk alasan yang sama bahwa gumpalan yang dapat berubah buruk, tetapi pada tingkat lebih rendah karena hanya mengacaukan kelas Anda, bukan mengacaukan seluruh program Anda.

Misalnya mungkin Anda dapat menyalin seluruh objek, menetapkan nilai baru untuk variabel dan mengunjungi salinan daripada mengunjungi diri sendiri. Tentunya itu tidak selalu mungkin atau efisien, Anda harus mencari alternatif berdasarkan kasus per kasus. Mungkin salinannya bisa dangkal sejauh menyangkut anak-anak (yaitu merujuk pada objek anak yang sama), yang mungkin cukup untuk membuatnya murah.

Mengenai penggunaan, Anda dapat menyimpulkan jenis seperti ini (kode belum teruji):

template <typename T, typename ARG>
TemporaryAssignment<T> temp_value(T &var, ARG &&newvalue) {
    return TemporaryAssignment(var, std::forward<ARG>(newValue));
}

Pemakaian:

auto t = temp_value(myVariable, myTemporaryValue);

Maka Anda memerlukan konstruktor bergerak untuk TemporaryAssignment:

template <typename TYPE> class TemporaryAssignment {
    // change data member
    TYPE *mVariable;
    TYPE mOriginalValue;
public:
    TemporaryAssignment(TYPE &inVariable, TYPE inValue) 
    : mVariable(&inVariable), mOriginalValue(std::move(inVariable)) {
        *mVariable = std::move(inValue);
    }
    TemporaryAssignment(TemporaryAssignment &&rhs) {
        mOriginalValue = std::move(rhs.mOriginalValue);
        mVariable = rhs.mVariable;
        rhs.mVariable = 0;
    }
    ~TypeAssignment() {
        using std::swap;
        if (mVariable) {
            swap(*mVariable, mOriginalValue);
        }
    }
    // can't remember whether this is needed
    TemporaryAssignment(const TemporaryAssignment &) = delete;
    TemporaryAssignment &operator=(const TemporaryAssignment &) = delete;
    TemporaryAssignment &operator=(TemporaryAssignment &&) = delete;
};

Saya berpikir sedikit tentang menentukan operator= untuk TemporaryAssignment sehingga membuat penggunaan terlihat seperti tugas, tapi saya tidak menghasilkan sesuatu yang bagus.

auto t = (temporary(myVariable) = myTemporaryValue);

masuk akal, tetapi Anda mungkin tidak ingin TemporaryAssignment memiliki operator= didefinisikan karena arti:

t = otherTemporaryValue;

belum tentu jelas dan mungkin tidak boleh diizinkan. Mungkin dengan kelas kedua yang akan dikembalikan temporary, dan itu kembali TemporaryAssignment dari itu operator=.


6
2017-11-14 17:13



swap bukan solusi terbaik, karena jika TYPE mempunyai sebuah std::string anggota, masalah kerusakan juga bisa terjadi. Saya pikir kita bisa menggunakan beberapa metode seperti ini:

char buffer[sizeof(TYPE)];
memcpy(buffer, &mVariable, sizeof(TYPE));
memcpy(&mVariable, &mOriginalValue, sizeof(TYPE));
memcpy(&mOriginalValue, buffer, sizeof(TYPE));

2
2017-11-15 07:53



Menambah jawaban dari Steve Jessop. Dari sudut pandang RIIA (atau RAII), destruktor kelas Anda mungkin berakhir dengan memperoleh sumber daya (memori) di mana seharusnya hanya melepaskan sumber daya. Untuk menghindari hal ini, Anda mungkin bisa berpikir untuk menggunakan myVar yang menunjuk ke objek yang tepat pada waktu yang tepat, sehingga objek baru dapat dibuat hanya pada waktu konstruksi atau akuisisi dan hanya menghapus dan menandakan penugasan ulang akan terjadi pada saat rilis .


0
2017-11-14 18:18