Vector3 có nên kế thừa từ Vector2 không?


18

Tôi đang tạo ra một vài lớp học Vector2(X & Y) và Vector3(X, Y & Z), nhưng tôi không biết liệu để làm cho Vector3kế thừa từ Vector2, hay để tái thực hiện các biến thành viên m_xm_ymột lần nữa? Những ưu và nhược điểm của mỗi bên (thừa kế so với xác định lại).

Chỉnh sửa: Tôi đang sử dụng C ++ (VS2010).


1
Tại sao không viết một lớp vectơ chung cho vectơ n chiều và sau đó (nếu cần) kế thừa một vectơ 2 và vectơ 3. Bạn cũng có thể sử dụng các mẫu cho lớp chung và các phiên bản kế thừa cho vectơ số nguyên và vectơ float. Chỉnh sửa: Tại sao bạn không sử dụng thư viện toán học được tối ưu hóa?
danijar

5
Bằng không căng của trí tưởng tượng "Vector3 là một Vector2", họ có thể đều kế thừa từ một VectorN mẹ mặc dù
Wim

1
Vâng, nhưng sau đó có lẽ bạn sẽ cần một bảng ảo và đó là một trong những trường hợp mà chi phí bộ nhớ và thời gian chạy có thể quan trọng. Lý tưởng nhất, Vector3chỉ nên là 3 floatsnhư bộ nhớ có liên quan. Không nói rằng điều đó là không thể, chỉ là tôi chưa bao giờ thấy điều đó trong một động cơ sản xuất.
Laurent Couvidou

2
Vâng, tôi nghĩ vậy. Cho đến khi bạn không cần bất cứ thứ gì khác floats. Bạn biết đấy, YAGNI, KISS, tất cả những thứ đó. Vector2, Vector3Vector4không có sự kế thừa và floatschỉ thực sự là tiêu chuẩn thực tế trong công cụ trò chơi.
Laurent Couvidou

1
Tôi hy vọng bạn có nghĩa là typedef float real;;).
Đánh dấu Ingram

Câu trả lời:


47

Không, không nên. Điều duy nhất bạn đang sử dụng từ thừa kế là xycác thành phần. Các phương thức được sử dụng trong một Vector2lớp sẽ không hữu ích trong một Vector3lớp, chúng có thể sẽ lấy các đối số khác nhau và thực hiện các hoạt động trên một số biến thành viên khác nhau.


+1, tôi nên chú ý nhiều hơn đến cửa sổ bật lên để tôi không viết những thứ dư thừa.
Matsemann

8
Kế thừa cổ điển lạm dụng . Một Vector3IS-KHÔNG-A Vector2(vì vậy nó không nên kế thừa), mà là AppleIS-A Fruit(vì vậy nó có thể kế thừa). Nếu bạn vặn vẹo tâm trí của mình, có Vector3HAS-A Vector2trong đó, nhưng mất hiệu năng và khó mã hóa có nghĩa là bạn sẽ viết các lớp hoàn toàn riêng biệt cho Vector3Vector2.
bobobobo

Nhưng bạn có thể (theo ý kiến ​​của tôi nên) viết một lớp vectơ n chiều để kế thừa một vectơ 2d và một vectơ 3d từ đó.
danijar

8

Có một điều tò mò bạn có thể làm với C ++ (Bạn đã không chỉ định ngôn ngữ và câu trả lời này chủ yếu là vì tôi nghĩ thật tuyệt khi thấy các lựa chọn thay thế, mặc dù tôi không thực sự tin rằng điều này hữu ích trong hầu hết các trường hợp.)

Sử dụng các mẫu bạn có thể làm một cái gì đó như thế này:

template <class T, class S, int U>
class VectorN
{
    protected:
        int _vec[U];
    public:
        S& operator+=(const S c)
        {
            for(int i = 0; i < U; i++)
            {
                _vec[i] += c.at(i);
            }
            return (S&)*this;
        }
        int at(int n) const
        {
            return _vec[n];
        }
};

template <class T>
class Vec2 : public VectorN<T,Vec2<T>,2>
{
    public:
        T& x;
        T& y;
        Vec2(T a, T b) : x(this->_vec[0]), y(this->_vec[1])
        {
            this->_vec[0] = a;
            this->_vec[1] = b;
        }
};

template <class T>
class Vec3 : public VectorN<T,Vec3<T>,3>
{
    public:
        T& x;
        T& y;
        T& z;
        Vec3(T a, T b, T c) : x(this->_vec[0]), y(this->_vec[1]), z(this->_vec[2])
        {
            this->_vec[0] = a;
            this->_vec[1] = b;
            this->_vec[2] = c;
        }
};

và điều này có thể được sử dụng như thế này:

int main(int argc, char* argv[])
{

    Vec2<int> v1(5,0);
    Vec2<int> v2(10,1);

    std::cout<<((v1+=v2)+=v2).x;
    return 0;
}

Như tôi đã nói, tôi không nghĩ rằng điều này hữu ích và nó có thể sẽ làm phức tạp cuộc sống của bạn khi bạn cố gắng thực hiện dấu chấm / chuẩn hóa / các công cụ khác và cố gắng chung chung với bất kỳ số lượng vectơ nào.


Vâng, tất cả các tính tổng quát có vẻ tốt, hầu hết thời gian bạn chỉ cần một vectơ 3 tiêu chuẩn với 3 thành phần dấu phẩy động - tất cả các dấu ngoặc góc sẽ tạo ra Vector3f vmột chút thú vị hơnVector3<float> v
bobobobo

@bobobobo Vâng, tôi đồng ý. Các lớp vectơ của tôi thường là vec2 và vec3 không có cha mẹ, nhưng vẫn tạo chúng thành các mẫu. Nếu viết Vector3 <float> làm phiền bạn, bạn luôn có thể gõ nó
Luke B.

.. Và bây giờ là đối số của lập trình viên C .. "nhưng còn thời gian biên dịch để sử dụng các mẫu thì sao?" Có thực sự đáng giá trong trường hợp này?
bobobobo

@bobobobo Tôi chưa bao giờ có bất kỳ vấn đề nào với thời gian biên dịch của mình: P, nhưng tôi chưa bao giờ làm việc với một dự án mà thời gian biên dịch sẽ là một vấn đề. Người ta có thể lập luận rằng thời gian biên dịch biện minh cho việc không sử dụng số float khi bạn cần số nguyên.
Luke B.

@bobobobo Với các cảnh báo rõ ràng và không bao gồm tệp nội tuyến của bạn trong tiêu đề, thời gian biên dịch sẽ không khác nhau. Ngoài ra, sự phình khung góc mẫu chỉ là một typedef.
Samaursa

7

Bất kể tốc độ, câu hỏi đầu tiên bạn nên tự hỏi khi thực hiện bất kỳ sự kế thừa nào là liệu bạn có đang sử dụng chúng một cách đa hình không. Cụ thể hơn, có bất kỳ tình huống nào bạn có thể thấy chính mình khi sử dụng Vector3như thể nó là một Vector2(mà, bằng cách kế thừa từ nó, bạn nói rõ ràng rằng Vector3 "là" Vector2 ".

Nếu không, thì bạn không nên sử dụng thừa kế. Bạn không nên sử dụng tính kế thừa để chia sẻ mã. Đó là những thành phần và chức năng bên ngoài dành cho, không phải là bạn sẽ chia sẻ bất kỳ mã nào giữa chúng.

Điều đó đang được nói, bạn có thể muốn các cách dễ dàng để chuyển đổi Vector3 s thành Vector2s, và trong trường hợp đó bạn có thể viết một quá tải toán tử sẽ ngầm cắt ngắn Vector3thành a Vector2. Nhưng bạn không nên thừa kế.


Cảm ơn, tôi nghĩ rằng điều đó đã làm nổi bật vấn đề, tôi đã xem xét vấn đề này từ quan điểm "chia sẻ mã" (nghĩa là không phải "nhập lại" các giá trị X & Y).
Đánh dấu Ingram

+1 câu trả lời tuyệt vời, không có sử dụng đa hình giữa các vectơ có kích thước khác nhau.
Luke B.

Đây là điều lớn nhất tôi sẽ thêm vào câu trả lời của riêng mình - chắc chắn +1. (Mặc dù có những trường hợp kỳ lạ mà tôi có thể tưởng tượng muốn đa hình - ví dụ, 2.5D 'heightmap' trò chơi nơi mà mọi thứ như kiểm tra khoảng cách, pathing, vv theo giáo luật sẽ muốn được thực hiện trong 2d nhưng bạn vẫn cần cung cấp tọa độ 3d cho các đối tượng)
Steven Stadnicki

@LukeB. Mặc dù trong trường hợp của OP tôi đồng ý rằng dường như không có lý do gì để thừa kế từ Vector2mà thừa kế từ một cơ sở Vector<N>? Điều đó làm cho ý nghĩa hoàn hảo. Hơn nữa, tại sao thừa kế tự động có nghĩa là hành vi đa hình? Một trong những điều tốt nhất về C ++ là bạn có thể có thừa kế chi phí bằng không. Không cần thêm bất kỳ phương thức ảo nào (bao gồm cả hàm hủy ảo) trong Vector<N>lớp cơ sở .
Samaursa

5

Không, vì mọi phương thức sẽ cần được ghi đè cũng như bạn sẽ không sử dụng thực sự kế thừa từ nó.

Nếu một cái gì đó, cả hai có thể thực hiện một giao diện Vector. Tuy nhiên, vì có lẽ bạn không muốn thêm / sub / dot / dst giữa Vector2 và Vector3 nên điều này sẽ có tác dụng phụ không mong muốn. Và có các thông số khác nhau, vv sẽ là một rắc rối.
Vì vậy, tôi thực sự không thể thấy bất kỳ ưu điểm của thừa kế / giao diện trong trường hợp này.

Một ví dụ là khung Libgdx, trong đó Vector2Vector3 không liên quan gì đến nhau, ngoài việc có cùng loại phương thức.


2

Nếu bạn có kế hoạch sử dụng mảng SIMD có khả năng là tốt nhất. Nếu bạn vẫn muốn sử dụng quá tải toán tử, bạn có thể xem xét sử dụng giao diện / mixin để truy cập mảng bên dưới - ví dụ, đây là điểm bắt đầu chỉ có (chưa được kiểm tra) Add.

Lưu ý cách tôi chưa cung cấp X/ Y/ Z, mỗi VectorXlớp sẽ kế thừa trực tiếp từ lớp này - vì những lý do tương tự được chỉ định bởi những người khác. Tuy nhiên, tôi đã thấy các mảng được sử dụng như các vectơ nhiều lần trong tự nhiên.

#include <xmmintrin.h>

class Vector
{
public:
    Vector(void)
    {
        Values = AllocArray();
    }

    virtual ~Vector(void) 
    { 
        _aligned_free(Values);
    }

    // Gets a pointer to the array that contains the vector.
    float* GetVector()
    {
        return Values;
    }

    // Gets the number of dimensions contained by the vector.
    virtual char GetDimensions() = 0;

    // An example of how the Vector2 Add would look.
    Vector2 operator+ (const Vector2& other)
    {
        return Vector2(Add(other.Values));
    }

protected:
    Vector(float* values)
    {
        // Assume it was created correctly.
        Values = values;
    }

    // The array of values in the vector.
    float* Values;

    // Adds another vector to this one (this + other)
    float* Add(float* other)
    {
        float* r = AllocArray();

#if SSE
        __m128 pv1 = _mm_load_ps(Values);
        __m128 pv2 = _mm_load_ps(other);
        __m128 pvr = _mm_load_ps(r);

        pvr = _mm_add_ps(pv1, pv2);
        _mm_store_ps(r, pvr);

#else
        char dims = GetDimensions();
        for(char i = 0; i < dims; i++)
            r[i] = Values[i] + other[i];
#endif

        return r;
    }

private:

    float* AllocArray()
    {
        // SSE float arrays need to be 16-byte aligned.
        return (float*) _aligned_malloc(GetDimensions() * sizeof(float), 16);
    }
};

Tuyên bố miễn trừ trách nhiệm: C ++ của tôi có thể bị mất, đã được một thời gian kể từ khi tôi sử dụng nó.


Đợi đã , việc bạn sử dụng _aligned_malloccó nghĩa là lỗi tôi đã mở không thực sự là một lỗi sao?
bobobobo

Bạn không nên sử dụng phôi con trỏ để đưa các giá trị của mình vào thanh __m128ghi, _mm_loadu_psthay vào đó bạn nên sử dụng . Một lớp mẫu tốt có ở đây dưới "vector class.zip"
bobobobo

@bobobobo Tôi sẽ cố gắng hết sức để chỉnh sửa - hãy chú ý từ chối trách nhiệm;).
Jonathan Dickinson

@bobobobo _mm_loadu_pssẽ phù hợp với bạn với cấu trúc đó (nơi _mm_load_pssẽ không). Tôi cũng đã thêm đề xuất của bạn - vui lòng chỉnh sửa câu hỏi nếu bạn cảm thấy rằng tôi đang sủa sai cây (đã được một thời gian kể từ khi tôi sử dụng C [++]).
Jonathan Dickinson

1

Một giả thuyết nghiêm trọng khác về việc thừa kế Vec3 từ Vec2 hoặc, được cho là có cả hai kế thừa từ một lớp Vector duy nhất: mã của bạn sẽ hoạt động rất nhiềucác hoạt động trên vectơ, thường trong các tình huống quan trọng về thời gian và rất có lợi cho bạn để đảm bảo rằng tất cả các hoạt động đó đều nhanh nhất có thể - nhiều hơn so với nhiều đối tượng khác không phải là khá phổ quát hoặc cấp thấp. Mặc dù một trình biên dịch tốt sẽ cố gắng hết sức để loại bỏ mọi chi phí thừa kế, nhưng bạn vẫn phụ thuộc nhiều vào trình biên dịch ở đó hơn là bạn muốn; thay vào đó, tôi sẽ xây dựng chúng thành các cấu trúc với càng ít chi phí càng tốt và thậm chí có thể thử và tạo ra hầu hết các chức năng sử dụng chúng (ngoại trừ những thứ như toán tử + không thực sự được trợ giúp) chứ không phải là phương thức trên cấu trúc. Tối ưu hóa sớm thường được đề nghị chống lại, và với lý do tuyệt vời,


1
-1 bởi vì: class và struct chỉ có hàm ý cấp quyền truy cập trong C ++ và OP dù sao cũng không chỉ định ngôn ngữ, các hàm thành viên không ảo có hàm ý hiệu suất tương tự như các hàm không phải thành viên và các hàm thành viên ảo (có khả năng thể hiện các vấn đề bạn quan tâm) chỉ tồn tại nếu bạn thực hiện chúng, không chỉ đơn giản bằng cách sử dụng tính kế thừa.

2
@JoshPetrie Điểm hợp lệ trên tất cả các mặt trận; Tôi có xu hướng 'mặc định' thành C / C ++ và vì vậy đã thấy câu hỏi qua ống kính đó. Tôi làm tin rằng có những hoạt động hợp pháp (cũng như khái niệm) lý do không đi con đường thừa kế, tâm trí bạn, nhưng tôi có thể đã tốt hơn nhiều vào các chi tiết cụ thể. Tôi sẽ thử và xem lại điều này và xem liệu tôi có thể đưa ra một kế toán tốt hơn không.
Steven Stadnicki
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.